diff --git a/Bitai.Ldap.Helper.sln b/Bitai.Ldap.Helper.sln index 2e4c7c0..01bbdf8 100644 --- a/Bitai.Ldap.Helper.sln +++ b/Bitai.Ldap.Helper.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 18 -VisualStudioVersion = 18.6.11822.322 stable +VisualStudioVersion = 18.6.11822.322 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bitai.LDAPHelper.DTO", "src\Bitai.LDAPHelper.DTO\Bitai.LDAPHelper.DTO.csproj", "{2386A126-F237-43FD-9A7C-FDF6718ACAD7}" EndProject @@ -24,6 +24,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C4F50587-D86 EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitai.LDAPHelper.Tests.Mocks", "tests\Bitai.LDAPHelper.Tests.Mocks\Bitai.LDAPHelper.Tests.Mocks.csproj", "{39BD48F5-8B64-469F-B6D7-9BB4582AE5F6}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bitai.LDAPHelper.LdapAdapters", "src\Bitai.LDAPHelper.LdapAdapters\Bitai.LDAPHelper.LdapAdapters.csproj", "{A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -94,6 +96,18 @@ Global {39BD48F5-8B64-469F-B6D7-9BB4582AE5F6}.Release|x64.Build.0 = Release|Any CPU {39BD48F5-8B64-469F-B6D7-9BB4582AE5F6}.Release|x86.ActiveCfg = Release|Any CPU {39BD48F5-8B64-469F-B6D7-9BB4582AE5F6}.Release|x86.Build.0 = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|x64.Build.0 = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|x86.ActiveCfg = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Debug|x86.Build.0 = Debug|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|Any CPU.Build.0 = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|x64.ActiveCfg = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|x64.Build.0 = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|x86.ActiveCfg = Release|Any CPU + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -104,6 +118,7 @@ Global {EF0E7C00-6116-46A7-BC21-6B30DE8361DD} = {D026C7BE-09AB-4BFD-B78E-1DF35734EBFD} {2D343BE2-3906-4541-B9A5-CBB9A866BE54} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} {39BD48F5-8B64-469F-B6D7-9BB4582AE5F6} = {02EA681E-C7D8-13C7-8484-4AC65E1B71E8} + {A5F5FA73-059E-411F-BBF1-A8AC4C8B65BC} = {C4F50587-D862-43DF-AFE6-D2EEC0B7454D} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DB0C2252-0846-4C49-970A-C7C68C82A4F3} diff --git a/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj b/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj index 7189504..c7bc7ce 100644 --- a/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj +++ b/demo/Bitai.LDAPHelper.Demo/Bitai.LDAPHelper.Demo.csproj @@ -19,7 +19,9 @@ + + diff --git a/demo/Bitai.LDAPHelper.Demo/Program.cs b/demo/Bitai.LDAPHelper.Demo/Program.cs index ef9e2fd..1ca9190 100644 --- a/demo/Bitai.LDAPHelper.Demo/Program.cs +++ b/demo/Bitai.LDAPHelper.Demo/Program.cs @@ -1,13 +1,14 @@ -using System; +using System; using System.Threading.Tasks; using Serilog; using System.Linq; using System.Collections.Generic; -using Bitai.LDAPHelper.Demo.Mocks.LdapAdapters; using Bitai.LDAPHelper.DTO; using Microsoft.Extensions.Configuration; -using Bitai.LDAPHelper.Demo.Mocks.LdapData; using Bitai.LDAPHelper.LdapAdapters.Novell; +using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; +using Bitai.LDAPHelper.Tests.Mocks.LdapData; +using Microsoft.Extensions.Logging; namespace Bitai.LDAPHelper.Demo; @@ -116,14 +117,20 @@ public static async Task Main(string[] args) if (implementation == ImplementationType.Mock) { Log.Information("Initializing Mock Implementation..."); - _context.ConnectionFactory = new PersistentMockLdapConnectionFactoryAdapter(); - + _context.ConnectionFactory = new MockLdapPersistentConnectionFactoryAdapter(); + + // Create logger for MockLdapDataSeeder + using var loggerFactory = LoggerFactory.Create(builder => { + builder.ClearProviders(); + builder.AddSerilog(Log.Logger); + }); + var logger = loggerFactory.CreateLogger(); + // Seed mock data - var seeder = new MockDataSeeder(_context); + var seeder = new MockLdapDataSeeder(logger); seeder.SeedAllData(); - //seeder.ExportGroupMembershipDataHierarchyToConsole(true); - //Console.ReadKey(); + seeder.PrintAllData(); // Set mock connection info (dummy values) _context.SelectedLdapServer = "mock-server"; @@ -542,4 +549,4 @@ private static async Task AskToRunAgain() await Main(new string[0]); } } -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper.LdapAdapters/Bitai.LDAPHelper.LdapAdapters.csproj b/src/Bitai.LDAPHelper.LdapAdapters/Bitai.LDAPHelper.LdapAdapters.csproj new file mode 100644 index 0000000..7396c7a --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/Bitai.LDAPHelper.LdapAdapters.csproj @@ -0,0 +1,33 @@ + + + + net10.0 + enable + enable + Bitai.LDAPHelper.LdapAdapters + 10.0.0 + Victor Bastidas (BITAI) + BITAI + LDAP Services Wrappers + Library to wrap an LDAP Service functionality. + © 2026 BITAI. MIT License. + https://github.com/bitai-cs/LDAPHelper + https://github.com/bitai-cs/LDAPHelper.git + ldap;authentication;authorization;directory;helper;oauth;openid;ad;security;identity + True + LICENSE.md + README.md + + + + + True + \ + + + True + \ + + + + diff --git a/src/Bitai.LDAPHelper.LdapAdapters/Enums.cs b/src/Bitai.LDAPHelper.LdapAdapters/Enums.cs new file mode 100644 index 0000000..b4a98aa --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/Enums.cs @@ -0,0 +1,21 @@ +namespace Bitai.LDAPHelper.LdapAdapters; + +/// +/// LDAP modification operation types +/// +public enum LdapModificationType +{ + Add = 0, + Delete = 1, + Replace = 2 +} + +/// +/// LDAP search scope values +/// +public enum LdapSearchScope +{ + ScopeBase = 0, + ScopeOne = 1, + ScopeSub = 2 +} diff --git a/src/Bitai.LDAPHelper.LdapAdapters/IConnectionInfo.cs b/src/Bitai.LDAPHelper.LdapAdapters/IConnectionInfo.cs new file mode 100644 index 0000000..6805831 --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/IConnectionInfo.cs @@ -0,0 +1,9 @@ +namespace Bitai.LDAPHelper.LdapAdapters; + +public interface IConnectionInfo +{ + short ConnectionTimeout { get; } + string Server { get; } + int ServerPort { get; } + bool UseSSL { get; } +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapAttributeAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapAttributeAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapAttributeAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapAttributeAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapAttributeSetAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapAttributeSetAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapAttributeSetAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapAttributeSetAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionAdapter.cs similarity index 87% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionAdapter.cs index 1350834..093bd97 100644 --- a/src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionAdapter.cs +++ b/src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionAdapter.cs @@ -16,11 +16,11 @@ public interface ILdapConnectionAdapter : IDisposable void ServerCertificateValidationByPass(); Task ConnectAsync(string host, int port); Task BindAsync(string userDN, string password); - Task SearchAsync(SearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly); + Task SearchAsync(ISearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly); ILdapAttributeSetAdapter CreateAttributeSet(); Task AddEntryAsync(string distinguishedName, ILdapAttributeSetAdapter attributes); ILdapModificationAdapter CreateModification(LdapModificationType type, string attributeName, object value); Task ModifyEntryAsync(string distinguishedName, IEnumerable modifications); Task DeleteEntryAsync(string distinguishedName); void Disconnect(); -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionFactoryAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionFactoryAdapter.cs similarity index 81% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionFactoryAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionFactoryAdapter.cs index 2583143..cd0ed38 100644 --- a/src/Bitai.LDAPHelper/LdapAdapters/ILdapConnectionFactoryAdapter.cs +++ b/src/Bitai.LDAPHelper.LdapAdapters/ILdapConnectionFactoryAdapter.cs @@ -1,5 +1,3 @@ -using System.Threading.Tasks; - namespace Bitai.LDAPHelper.LdapAdapters; /// @@ -8,8 +6,8 @@ namespace Bitai.LDAPHelper.LdapAdapters; public interface ILdapConnectionFactoryAdapter { Task CreateConnectionAsync( - ConnectionInfo connectionInfo, + IConnectionInfo connectionInfo, string userAccount, string password, bool bindRequired = true); -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapEntryAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapEntryAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapEntryAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapEntryAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapMessageAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapMessageAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapMessageAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapMessageAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapModificationAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapModificationAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapModificationAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapModificationAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapSearchConstraintsAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapSearchConstraintsAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapSearchConstraintsAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapSearchConstraintsAdapter.cs diff --git a/src/Bitai.LDAPHelper/LdapAdapters/ILdapSearchQueueAdapter.cs b/src/Bitai.LDAPHelper.LdapAdapters/ILdapSearchQueueAdapter.cs similarity index 100% rename from src/Bitai.LDAPHelper/LdapAdapters/ILdapSearchQueueAdapter.cs rename to src/Bitai.LDAPHelper.LdapAdapters/ILdapSearchQueueAdapter.cs diff --git a/src/Bitai.LDAPHelper.LdapAdapters/ISearchLimits.cs b/src/Bitai.LDAPHelper.LdapAdapters/ISearchLimits.cs new file mode 100644 index 0000000..281c880 --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/ISearchLimits.cs @@ -0,0 +1,9 @@ +namespace Bitai.LDAPHelper.LdapAdapters; + +public interface ISearchLimits +{ + string BaseDN { get; set; } + LdapSearchScope LdapSearchScope { get; set; } + int MaxSearchResults { get; set; } + int MaxSearchTimeout { get; set; } +} diff --git a/src/Bitai.LDAPHelper.LdapAdapters/LICENSE.md b/src/Bitai.LDAPHelper.LdapAdapters/LICENSE.md new file mode 100644 index 0000000..e263634 --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2026 BITAI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/Bitai.LDAPHelper.LdapAdapters/README.md b/src/Bitai.LDAPHelper.LdapAdapters/README.md new file mode 100644 index 0000000..ee1b0f4 --- /dev/null +++ b/src/Bitai.LDAPHelper.LdapAdapters/README.md @@ -0,0 +1,110 @@ +# Bitai.LDAPHelper.LdapAdapters + +`Bitai.LDAPHelper.LdapAdapters` defines the LDAP adapter contracts used by the Bitai LDAP Helper libraries. It is a small abstraction layer that keeps the core helper logic independent from any specific LDAP client implementation, such as `Novell.Directory.Ldap.NETStandard`. + +This package contains interfaces and enums only. It does not open LDAP connections by itself and does not include a concrete LDAP provider. + +## Purpose + +Use this project when you need to: + +- Implement a concrete LDAP provider for `Bitai.LDAPHelper`. +- Test LDAP-dependent code without binding directly to a real directory server. +- Isolate application logic from vendor-specific LDAP client APIs. +- Standardize LDAP operations such as bind, search, add, modify, delete, and attribute access. + +## Target Framework + +- .NET 10.0 +- Nullable reference types enabled +- Implicit global usings enabled + +## Package Metadata + +- Package ID: `Bitai.LDAPHelper.LdapAdapters` +- Version: `10.0.0` +- Repository: `https://github.com/bitai-cs/LDAPHelper` +- License: MIT + +## Main Contracts + +### Connection and Factory + +- `ILdapConnectionFactoryAdapter` creates configured LDAP connections from `IConnectionInfo` and credentials. +- `ILdapConnectionAdapter` represents a connected LDAP session and exposes bind, search, add, modify, delete, disconnect, and dispose operations. +- `IConnectionInfo` describes LDAP server address, port, SSL usage, and timeout settings. + +### Search + +- `ISearchLimits` describes base DN, search scope, result limits, and timeout limits. +- `ILdapSearchQueueAdapter` abstracts queued LDAP search responses. +- `ILdapMessageAdapter` distinguishes search result entries from search completion messages. + +### Entries and Attributes + +- `ILdapEntryAdapter` exposes an LDAP entry distinguished name and its attribute set. +- `ILdapAttributeSetAdapter` creates and retrieves LDAP attributes by name. +- `ILdapAttributeAdapter` exposes attribute values as string, string array, byte array, or byte array collection. + +### Modifications + +- `ILdapModificationAdapter` describes an LDAP attribute modification. +- `LdapModificationType` supports add, delete, and replace operations. +- `LdapSearchScope` supports base, one-level, and subtree LDAP searches. + +## Implementing an Adapter + +A concrete provider typically implements `ILdapConnectionFactoryAdapter` and `ILdapConnectionAdapter`, then maps the Bitai contracts to the provider-specific LDAP client. + +```csharp +using Bitai.LDAPHelper.LdapAdapters; + +public sealed class CustomLdapConnectionFactoryAdapter : ILdapConnectionFactoryAdapter +{ + public async Task CreateConnectionAsync( + IConnectionInfo connectionInfo, + string userAccount, + string password, + bool bindRequired = true) + { + var connection = new CustomLdapConnectionAdapter(); + connection.ConnectionTimeout = connectionInfo.ConnectionTimeout; + connection.SecureSocketLayer = connectionInfo.UseSSL; + + await connection.ConnectAsync(connectionInfo.Server, connectionInfo.ServerPort); + + if (bindRequired) + { + await connection.BindAsync(userAccount, password); + } + + return connection; + } +} +``` + +The adapter is then passed into higher-level `Bitai.LDAPHelper` services such as authentication, search, account management, and group membership validation. + +## Design Notes + +- The package intentionally contains no dependency on a specific LDAP SDK. +- Adapter implementations should preserve provider exceptions only when callers can handle them meaningfully; otherwise, translate them into domain-level exceptions used by the consuming library. +- `ILdapConnectionAdapter` implements `IDisposable`; concrete implementations should release network resources and provider-specific handles consistently. +- `ServerCertificateValidationByPass` exists for compatibility with provider adapters. Use it only in controlled development or test environments. + +## Build + +From the repository root: + +```powershell +dotnet build src/Bitai.LDAPHelper.LdapAdapters/Bitai.LDAPHelper.LdapAdapters.csproj +``` + +## Related Projects + +- `Bitai.LDAPHelper`: Core LDAP helper services that consume these contracts. +- `Bitai.LDAPHelper.Tests.Mocks`: In-memory mock implementations for tests and demos. + +## License + +This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md). diff --git a/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj b/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj index 833e4ce..785c96f 100644 --- a/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj +++ b/src/Bitai.LDAPHelper/Bitai.LDAPHelper.csproj @@ -7,10 +7,10 @@ LDAP Services Wrappers Library to wrap Novell.Directory.Ldap.NETStandard functionality to make LDAP common queries to search accounts and objects in a Directory Service. © 2026 BITAI. All rights reserved. - 10.0.0 - 10.0.0 + 10.1.0 + 10.1.0 hierarchy_32.png - 10.0.0 + 10.1.0 true Bitai.LDAPHelper Bitai.LDAPHelper @@ -43,6 +43,7 @@ + diff --git a/src/Bitai.LDAPHelper/ConnectionInfo.cs b/src/Bitai.LDAPHelper/ConnectionInfo.cs index e66b8c4..0c76ed1 100644 --- a/src/Bitai.LDAPHelper/ConnectionInfo.cs +++ b/src/Bitai.LDAPHelper/ConnectionInfo.cs @@ -1,24 +1,26 @@ -namespace Bitai.LDAPHelper +using Bitai.LDAPHelper.LdapAdapters; + +namespace Bitai.LDAPHelper { - public class ConnectionInfo - { - public ConnectionInfo(string server, int port, bool useSSL, short connectionTimeout) - { - Server = server; - ServerPort = port; - UseSSL = useSSL; - ConnectionTimeout = connectionTimeout; - } + public class ConnectionInfo : IConnectionInfo + { + public ConnectionInfo(string server, int port, bool useSSL, short connectionTimeout) + { + Server = server; + ServerPort = port; + UseSSL = useSSL; + ConnectionTimeout = connectionTimeout; + } - public string Server { get; } + public string Server { get; } - public int ServerPort { get; } + public int ServerPort { get; } - public bool UseSSL { get; } + public bool UseSSL { get; } - /// - /// Connection timeout in seconds - /// - public short ConnectionTimeout { get; } - } + /// + /// Connection timeout in seconds + /// + public short ConnectionTimeout { get; } + } } diff --git a/src/Bitai.LDAPHelper/Enums.cs b/src/Bitai.LDAPHelper/Enums.cs index 55608a0..7d135a1 100644 --- a/src/Bitai.LDAPHelper/Enums.cs +++ b/src/Bitai.LDAPHelper/Enums.cs @@ -9,25 +9,5 @@ public enum LdapServerDefaultPorts : int DefaultSslPort = 636, DefaultGlobalCatalogPort = 3268, DefaultGlobalCatalogSslPort = 3269 - } - - /// - /// LDAP modification operation types - /// - public enum LdapModificationType - { - Add = 0, - Delete = 1, - Replace = 2 - } - - /// - /// LDAP search scope values - /// - public enum LdapSearchScope - { - ScopeBase = 0, - ScopeOne = 1, - ScopeSub = 2 - } -} \ No newline at end of file + } +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionAdapter.cs b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionAdapter.cs index 8f8bd63..0abe698 100644 --- a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionAdapter.cs +++ b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionAdapter.cs @@ -91,7 +91,7 @@ public async Task BindAsync(string userDN, string password) { /// Whether to return only attribute types (no values). /// Optional search constraints adapter which will be translated to the Novell constraints type. /// A task that produces an wrapping the resulting . - public async Task SearchAsync(SearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly) { + public async Task SearchAsync(ISearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly) { var novellConstraints = new LdapSearchConstraints() { ServerTimeLimit = searchLimits.MaxSearchTimeout, MaxResults = searchLimits.MaxSearchResults @@ -220,4 +220,4 @@ protected virtual void Dispose(bool disposing) { _disposed = true; } } -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionFactoryAdapter.cs b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionFactoryAdapter.cs index d50fcb4..523406b 100644 --- a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionFactoryAdapter.cs +++ b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapConnectionFactoryAdapter.cs @@ -10,7 +10,7 @@ namespace Bitai.LDAPHelper.LdapAdapters.Novell; public class NovellLdapConnectionFactoryAdapter : ILdapConnectionFactoryAdapter { public async Task CreateConnectionAsync( - ConnectionInfo connectionInfo, + IConnectionInfo connectionInfo, string userAccount, string password, bool bindRequired = true) { @@ -40,4 +40,4 @@ public async Task CreateConnectionAsync( return adapter; } -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapSearchQueueAdapter.cs b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapSearchQueueAdapter.cs index 80b3f90..c5f4582 100644 --- a/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapSearchQueueAdapter.cs +++ b/src/Bitai.LDAPHelper/LdapAdapters/Novell/NovellLdapSearchQueueAdapter.cs @@ -1,4 +1,4 @@ -using Novell.Directory.Ldap; +using Novell.Directory.Ldap; namespace Bitai.LDAPHelper.LdapAdapters.Novell; @@ -17,4 +17,4 @@ public ILdapMessageAdapter GetResponse() { LdapMessage message = _searchQueue.GetResponse(); return message != null ? new NovellLdapMessageAdapter(message) : null; } -} \ No newline at end of file +} diff --git a/src/Bitai.LDAPHelper/SearchLimits.cs b/src/Bitai.LDAPHelper/SearchLimits.cs index cf14ceb..a16bcf7 100644 --- a/src/Bitai.LDAPHelper/SearchLimits.cs +++ b/src/Bitai.LDAPHelper/SearchLimits.cs @@ -1,11 +1,13 @@ -namespace Bitai.LDAPHelper +using Bitai.LDAPHelper.LdapAdapters; + +namespace Bitai.LDAPHelper { - public class SearchLimits - { - /// - /// The base distinguished name (DN) to search from. - /// - public string BaseDN { get; set; } = "DC=com"; + public class SearchLimits : ISearchLimits + { + /// + /// The base distinguished name (DN) to search from. + /// + public string BaseDN { get; set; } = "DC=com"; /// /// The scope of the search operation, which determines how deep the search will @@ -20,14 +22,15 @@ public class SearchLimits /// public int MaxSearchResults { get; set; } = 1000; - /// - /// Maximum number of seconds that the server waits when returning search results. - /// The search operation will be terminated with an LdapException.TIME_LIMIT_EXCEEDED if the operation exceeds the time limit. - /// - public int MaxSearchTimeout { get; set; } = 60; + /// + /// Maximum number of seconds that the server waits when returning search results. + /// The search operation will be terminated with an LdapException.TIME_LIMIT_EXCEEDED if the operation exceeds the time limit. + /// + public int MaxSearchTimeout { get; set; } = 60; - public SearchLimits(string baseDN) { - this.BaseDN = baseDN; - } - } + public SearchLimits(string baseDN) + { + this.BaseDN = baseDN; + } + } } diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj b/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj index f554382..504b7be 100644 --- a/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj @@ -4,18 +4,37 @@ net10.0 enable enable - 10.0.0 - 10.0.0 - 10.0.0 + 10.1.0 + 10.1.0 + 10.1.0 Viko Bastidas (BITAI) - © 2026 BITAI. All rights reserved. + © 2026 BITAI. MIT License. https://github.com/bitai-cs/LDAPHelper.git GitHub - ldap, authentication, directory, helper + ldap;authentication;authorization;directory;helper;oauth;openid;ad;security;identity + Bitai.LDAPHelper.Tests.Mocks + BITAI + LDAP Services Wrappers + Mocks for testing BITAI.LDAPHelper library. + https://github.com/bitai-cs/LDAPHelper + True + LICENSE.md + README.md + + + True + \ + + + True + \ + + + diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/LICENSE.md b/tests/Bitai.LDAPHelper.Tests.Mocks/LICENSE.md new file mode 100644 index 0000000..e263634 --- /dev/null +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LICENSE.md @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2026 BITAI + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionAdapter.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionAdapter.cs index df709d0..c57cf7f 100644 --- a/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionAdapter.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionAdapter.cs @@ -40,7 +40,7 @@ public Task BindAsync(string userDN, string password) { return Task.CompletedTask; } - public virtual Task SearchAsync(SearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly) { + public virtual Task SearchAsync(ISearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly) { var matchingEntries = new List(); // Find matching results based on filter @@ -114,4 +114,4 @@ protected virtual void Dispose(bool disposing) { _disposed = true; } } -} \ No newline at end of file +} diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionFactoryAdapter.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionFactoryAdapter.cs index 5839c9c..dd8ed70 100644 --- a/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionFactoryAdapter.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapConnectionFactoryAdapter.cs @@ -11,7 +11,7 @@ public MockLdapConnectionFactoryAdapter(MockLdapConnectionAdapter connection) { } public async Task CreateConnectionAsync( - ConnectionInfo connectionInfo, + IConnectionInfo connectionInfo, string userAccount, string password, bool bindRequired = true) { @@ -34,4 +34,4 @@ public async Task CreateConnectionAsync( return (ILdapConnectionAdapter)_connection; } -} \ No newline at end of file +} diff --git a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionAdapter.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionAdapter.cs similarity index 95% rename from demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionAdapter.cs rename to tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionAdapter.cs index a330e13..a3cb143 100644 --- a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionAdapter.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionAdapter.cs @@ -1,20 +1,16 @@ -using System; -using System.Collections.Generic; using System.Text; -using System.Threading.Tasks; using Bitai.LDAPHelper.LdapAdapters; -using Bitai.LDAPHelper.Demo.Mocks.LdapData; -using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; +using Bitai.LDAPHelper.Tests.Mocks.LdapData; -namespace Bitai.LDAPHelper.Demo.Mocks.LdapAdapters; +namespace Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; -public class PersistentMockLdapConnectionAdapter : MockLdapConnectionAdapter +public class MockLdapPersistentConnectionAdapter : MockLdapConnectionAdapter { - private readonly MockDataStore _dataStore; + private readonly MockLdapDataStore _dataStore; - public PersistentMockLdapConnectionAdapter() : base() + public MockLdapPersistentConnectionAdapter() : base() { - _dataStore = MockDataStore.Instance; + _dataStore = MockLdapDataStore.Instance; } public override Task AddEntryAsync(string distinguishedName, ILdapAttributeSetAdapter attributes) @@ -145,7 +141,7 @@ public override Task DeleteEntryAsync(string distinguishedName) } public override Task SearchAsync( - SearchLimits searchLimits, + ISearchLimits searchLimits, string searchFilter, string[] attributeNames, bool typesOnly) diff --git a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionFactoryAdapter.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionFactoryAdapter.cs similarity index 52% rename from demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionFactoryAdapter.cs rename to tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionFactoryAdapter.cs index b426ccc..c724fef 100644 --- a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapAdapters/PersistentMockLdapConnectionFactoryAdapter.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapAdapters/MockLdapPersistentConnectionFactoryAdapter.cs @@ -1,19 +1,18 @@ -using System.Threading.Tasks; using Bitai.LDAPHelper.LdapAdapters; -namespace Bitai.LDAPHelper.Demo.Mocks.LdapAdapters; +namespace Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; -public class PersistentMockLdapConnectionFactoryAdapter : ILdapConnectionFactoryAdapter +public class MockLdapPersistentConnectionFactoryAdapter : ILdapConnectionFactoryAdapter { - private readonly PersistentMockLdapConnectionAdapter _connection; + private readonly MockLdapPersistentConnectionAdapter _connection; - public PersistentMockLdapConnectionFactoryAdapter() + public MockLdapPersistentConnectionFactoryAdapter() { - _connection = new PersistentMockLdapConnectionAdapter(); + _connection = new MockLdapPersistentConnectionAdapter(); } public Task CreateConnectionAsync( - ConnectionInfo connectionInfo, + IConnectionInfo connectionInfo, string userAccount, string password, bool bindRequired = true) @@ -22,4 +21,4 @@ public Task CreateConnectionAsync( _connection.BindAsync(userAccount, password); return Task.FromResult(_connection); } -} \ No newline at end of file +} diff --git a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataSeeder.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataSeeder.cs similarity index 92% rename from demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataSeeder.cs rename to tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataSeeder.cs index 3facc56..1941c40 100644 --- a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataSeeder.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataSeeder.cs @@ -1,17 +1,13 @@ -using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; -using Serilog; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; +using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; +using Microsoft.Extensions.Logging; -namespace Bitai.LDAPHelper.Demo.Mocks.LdapData; +namespace Bitai.LDAPHelper.Tests.Mocks.LdapData; -public class MockDataSeeder +public class MockLdapDataSeeder { - private readonly DemoContext _context; - private readonly MockDataStore _dataStore; + private readonly MockLdapDataStore _dataStore; private readonly Random _random; + private readonly ILogger _logger; // Progressive RID counters (thread-safe) private static int _nextUserRid = 1000; // Users start from 1000 @@ -19,20 +15,20 @@ public class MockDataSeeder private static int _nextComputerRid = 3000; // Computers start from 3000 private static int _nextOtherRid = 4000; // Other objects start from 4000 - public MockDataSeeder(DemoContext context) { - _context = context; - _dataStore = MockDataStore.Instance; + public MockLdapDataSeeder(ILogger logger) { + _dataStore = MockLdapDataStore.Instance; _random = new Random(); + _logger = logger; } public void SeedAllData() { _dataStore.Clear(); - Log.Information("=".PadRight(60, '=')); - Log.Information("MOCK DATA SEEDING STARTED"); - Log.Information("=".PadRight(60, '=')); - Log.Information($"RID counters initialized - Users: {_nextUserRid}, Groups: {_nextGroupRid}, Computers: {_nextComputerRid}"); - Log.Information("=".PadRight(60, '=')); + _logger.LogInformation("=".PadRight(60, '=')); + _logger.LogInformation("MOCK DATA SEEDING STARTED"); + _logger.LogInformation("=".PadRight(60, '=')); + _logger.LogInformation($"RID counters initialized - Users: {_nextUserRid}, Groups: {_nextGroupRid}, Computers: {_nextComputerRid}"); + _logger.LogInformation("=".PadRight(60, '=')); // Create domain structure SeedDomainStructure(); @@ -61,15 +57,25 @@ public void SeedAllData() { // Create additional relationships SeedAdditionalRelationships(); - Log.Information("=".PadRight(60, '=')); - Log.Information($"MOCK DATA SEEDING COMPLETED"); - Log.Information($"Total Entries Created: {_dataStore.Count}"); - Log.Information($"Users: {CountEntriesByObjectClass("user")}"); - Log.Information($"Groups: {CountEntriesByObjectClass("group")}"); - Log.Information($"Computers: {CountEntriesByObjectClass("computer")}"); - Log.Information($"OUs: {CountEntriesByObjectClass("organizationalUnit")}"); - Log.Information($"Final RID counters - Users: {_nextUserRid}, Groups: {_nextGroupRid}, Computers: {_nextComputerRid}"); - Log.Information("=".PadRight(60, '=')); + _logger.LogInformation("=".PadRight(60, '=')); + _logger.LogInformation($"MOCK DATA SEEDING COMPLETED"); + _logger.LogInformation($"Total Entries Created: {_dataStore.Count}"); + _logger.LogInformation($"Users: {CountEntriesByObjectClass("user")}"); + _logger.LogInformation($"Groups: {CountEntriesByObjectClass("group")}"); + _logger.LogInformation($"Computers: {CountEntriesByObjectClass("computer")}"); + _logger.LogInformation($"OUs: {CountEntriesByObjectClass("organizationalUnit")}"); + _logger.LogInformation($"Final RID counters - Users: {_nextUserRid}, Groups: {_nextGroupRid}, Computers: {_nextComputerRid}"); + _logger.LogInformation("=".PadRight(60, '=')); + } + + public void PrintAllData() { + _logger.LogInformation("=".PadRight(60, '=')); + _logger.LogInformation($"LISTING ALL ENTRIES: {_dataStore.Count}"); + foreach (var entry in _dataStore.GetAllEntries()) + { + _logger.LogInformation(entry.DistinguishedName); + } + _logger.LogInformation("=".PadRight(60, '=')); } #region Domain Structure @@ -196,16 +202,16 @@ private void SeedStandardUsers() { } private void SeedDemoSpecificUsers() { - //// Create user for CreateUserAccount demo - //var newUser = new UserData( - // "Victor", - // "Bastidas", - // "victor.bastidas", - // "HOLDING", - // "OU=Seniors,OU=DevOps,OU=IT,DC=holding,DC=latam,DC=com", - // "Senior DevOps Engineer" - //); - //CreateMockUser(newUser); + // Create user for CreateUserAccount demo + var newUser = new UserData( + "Victor", + "Bastidas", + "victor.bastidas", + "HOLDING", + "OU=Seniors,OU=DevOps,OU=IT,DC=holding,DC=latam,DC=com", + "Senior DevOps Engineer" + ); + CreateMockUser(newUser); // Create user for password reset demo var passwordResetUser = new UserData( @@ -295,7 +301,7 @@ private void CreateMockUser(UserData user) { } _dataStore.AddOrUpdateEntry(userEntry); - Log.Debug($"Created user: {user.SAMAccountName} ({distinguishedName}) with RID: {rid}"); + _logger.LogDebug($"Created user: {user.SAMAccountName} ({distinguishedName}) with RID: {rid}"); } #endregion @@ -378,7 +384,7 @@ private void CreateMockGroup(GroupData group) { groupEntry.AddAttribute("distinguishedName", groupDistinguishedName); _dataStore.AddOrUpdateEntry(groupEntry); - Log.Debug($"Created group: {group.SAMAccountName} ({groupDistinguishedName}) with RID: {rid}"); + _logger.LogDebug($"Created group: {group.SAMAccountName} ({groupDistinguishedName}) with RID: {rid}"); } #endregion @@ -438,7 +444,7 @@ private void CreateMockComputer(ComputerData computer) { computerEntry.AddAttribute("distinguishedName", distinguishedName); _dataStore.AddOrUpdateEntry(computerEntry); - Log.Debug($"Created computer: {computer.Name} ({distinguishedName}) with RID: {rid}"); + _logger.LogDebug($"Created computer: {computer.Name} ({distinguishedName}) with RID: {rid}"); } #endregion @@ -701,4 +707,4 @@ public ComputerData(string name, string containerDN, string description, string } #endregion -} \ No newline at end of file +} diff --git a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataStore.cs b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataStore.cs similarity index 84% rename from demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataStore.cs rename to tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataStore.cs index 4763c01..2b4c8b6 100644 --- a/demo/Bitai.LDAPHelper.Demo/Mocks/LdapData/MockDataStore.cs +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/LdapData/MockLdapDataStore.cs @@ -1,20 +1,16 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; -namespace Bitai.LDAPHelper.Demo.Mocks.LdapData; +namespace Bitai.LDAPHelper.Tests.Mocks.LdapData; -public class MockDataStore +public class MockLdapDataStore { - private static readonly Lazy _instance = new Lazy(() => new MockDataStore()); - public static MockDataStore Instance => _instance.Value; + private static readonly Lazy _instance = new Lazy(() => new MockLdapDataStore()); + public static MockLdapDataStore Instance => _instance.Value; private readonly Dictionary _entries; private readonly ReaderWriterLockSlim _lock; - private MockDataStore() { + private MockLdapDataStore() { _entries = new Dictionary(StringComparer.OrdinalIgnoreCase); _lock = new ReaderWriterLockSlim(); } @@ -94,4 +90,4 @@ public int Count { } #endregion -} \ No newline at end of file +} diff --git a/tests/Bitai.LDAPHelper.Tests.Mocks/README.md b/tests/Bitai.LDAPHelper.Tests.Mocks/README.md new file mode 100644 index 0000000..915142a --- /dev/null +++ b/tests/Bitai.LDAPHelper.Tests.Mocks/README.md @@ -0,0 +1,146 @@ +# Bitai.LDAPHelper.Tests.Mocks + +`Bitai.LDAPHelper.Tests.Mocks` provides in-memory LDAP adapter implementations for testing and demo scenarios in the Bitai LDAP Helper ecosystem. It implements the adapter contracts consumed by `Bitai.LDAPHelper`, allowing authentication, search, account management, and group membership workflows to be exercised without a live LDAP or Active Directory server. + +This project is intended for tests, demos, and local development support. It should not be used as a production LDAP provider. + +## Purpose + +Use this project when you need to: + +- Unit test LDAP-dependent code without network access. +- Verify how `Bitai.LDAPHelper` services call LDAP adapter contracts. +- Seed predictable directory entries for repeatable test cases. +- Record add, modify, and delete operations performed by higher-level services. +- Run demo flows in an offline mock mode. + +## Target Framework + +- .NET 10.0 +- Nullable reference types enabled +- Implicit global usings enabled + +## Package Metadata + +- Package ID: `Bitai.LDAPHelper.Tests.Mocks` +- Version: `10.0.0` +- Repository: `https://github.com/bitai-cs/LDAPHelper` +- License: MIT + +## Project Dependencies + +This project references: + +- `Bitai.LDAPHelper` +- `Bitai.LDAPHelper.LdapAdapters` +- `Bitai.LDAPHelper.DTO` + +Those references provide the public helper services, adapter contracts, DTOs, and LDAP-related domain types used by the mocks. + +## Main Components + +### Adapter Mocks + +- `MockLdapConnectionAdapter` provides a configurable in-memory LDAP connection. +- `MockLdapConnectionFactoryAdapter` returns a supplied mock connection and simulates connect and bind behavior. +- `MockLdapPersistenConnectionAdapter` extends the basic mock connection with a shared in-memory data store. +- `MockLdapPersistentConnectionFactoryAdapter` creates the persistent mock connection used by richer demo scenarios. +- `MockLdapEntryAdapter`, `MockLdapAttributeSetAdapter`, `MockLdapAttributeAdapter`, `MockLdapMessageAdapter`, `MockLdapSearchQueueAdapter`, and `MockLdapModificationAdapter` implement the lower-level LDAP contracts. + +### Mock Data + +- `MockLdapDataStore` is a thread-safe singleton store keyed by distinguished name. +- `MockLdapDataSeeder` populates the store with representative domains, organizational units, users, computers, groups, and group memberships. + +## Basic Unit Test Usage + +The simplest pattern is to create a mock connection, register search results for expected filters, and pass a mock factory into the service under test. + +```csharp +using Bitai.LDAPHelper; +using Bitai.LDAPHelper.DTO; +using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; + +var connection = new MockLdapConnectionAdapter(); + +var userEntry = new MockLdapEntryAdapter("CN=John Smith,OU=Users,DC=example,DC=com"); +userEntry.AddAttribute("cn", "John Smith"); +userEntry.AddAttribute("sAMAccountName", "john.smith"); +userEntry.AddAttribute("distinguishedName", userEntry.DistinguishedName); + +connection.AddSearchResult("(sAMAccountName=john.smith)", new List +{ + userEntry +}); + +var factory = new MockLdapConnectionFactoryAdapter(connection); + +var connectionInfo = new ConnectionInfo("localhost", 389, useSSL: false, connectionTimeout: 15); +var credential = new LDAPDomainAccountCredential("EXAMPLE", "service.account", "StrongPassword!"); +var searchLimits = new SearchLimits("DC=example,DC=com") +{ + MaxSearchResults = 10, + MaxSearchTimeout = 15 +}; + +var searcher = new Searcher(connectionInfo, searchLimits, credential, factory); +``` + +## Verifying Write Operations + +`MockLdapConnectionAdapter` records created, modified, and deleted entries so tests can assert side effects. + +```csharp +var connection = new MockLdapConnectionAdapter(); +var factory = new MockLdapConnectionFactoryAdapter(connection); + +// Execute code that creates, modifies, or deletes LDAP entries. + +Assert.NotEmpty(connection.CreatedEntries); +Assert.NotEmpty(connection.Modifications); +Assert.NotEmpty(connection.DeletedEntries); +``` + +## Seeded Directory Usage + +For integration-style tests or demos, use the persistent connection factory with `MockLdapDataSeeder`. + +```csharp +using Bitai.LDAPHelper.Tests.Mocks.LdapAdapters; +using Bitai.LDAPHelper.Tests.Mocks.LdapData; +using Microsoft.Extensions.Logging.Abstractions; + +var seeder = new MockLdapDataSeeder(NullLogger.Instance); +seeder.SeedAllData(); + +var factory = new MockLdapPersistentConnectionFactoryAdapter(); +``` + +The seeded data includes representative LATAM domain roots, organizational units, user accounts, groups, computers, and nested memberships used by the repository tests and demo project. + +## Mock Behavior Notes + +- `MockLdapConnectionAdapter.ConnectAsync` rejects empty hosts, `unknown`, `0.0.0.0`, and non-positive ports. +- `MockLdapConnectionAdapter.BindAsync` rejects empty credentials and intentionally weak or suspicious sample credentials used by tests. +- Search matching in the basic mock connection is filter-pattern based; register the expected filter text before executing the service under test. +- The persistent mock connection searches the shared `MockLdapDataStore` and applies a richer in-memory behavior model. +- `ServerCertificateValidationByPass` throws in mock classes because certificate bypass behavior is not meaningful in an in-memory implementation. + +## Build + +From the repository root: + +```powershell +dotnet build tests/Bitai.LDAPHelper.Tests.Mocks/Bitai.LDAPHelper.Tests.Mocks.csproj +``` + +## Related Projects + +- `Bitai.LDAPHelper.LdapAdapters`: Adapter contracts implemented by this project. +- `Bitai.LDAPHelper`: Core LDAP helper services tested with these mocks. +- `Bitai.LDAPHelper.Tests`: Test suite that consumes these mocks. +- `Bitai.LDAPHelper.Demo`: Demo application that can run against the persistent mock data store. + +## License + +This project is licensed under the MIT License. See [LICENSE.md](LICENSE.md).