Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -365,8 +365,8 @@ public class SearcherTests
[Fact]
public async Task GetUser_UsingMockAdapter_ShouldReturnStubbedUser()
{
// 1. Instantiate in-memory mock connection
var mockConnection = new MockLdapConnectionAdapter();
// 1. Instantiate persistent mock connection
var mockConnection = new MockLdapPersistentConnectionAdapter();

// 2. Add stubbed search response
var stubEntry = new MockLdapEntryAdapter("CN=Test User,OU=Users,DC=example,DC=com");
Expand All @@ -376,7 +376,7 @@ public class SearcherTests
mockConnection.AddSearchResult("(sAMAccountName=testuser)", new List<MockLdapEntryAdapter> { stubEntry });

// 3. Pass mock factory to Searcher
var factory = new MockLdapConnectionFactoryAdapter(mockConnection);
var factory = new MockLdapPersistentConnectionFactoryAdapter();
var searcher = new Searcher(
new ConnectionInfo("localhost", 389, false, 5),
new SearchLimits("DC=example,DC=com"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
<Company>BITAI</Company>
<Product>LDAP Services Wrappers</Product>
<Copyright>© 2026 BITAI. All rights reserved.</Copyright>
<Version>10.0.0</Version>
<AssemblyVersion>10.0.0</AssemblyVersion>
<FileVersion>10.0.0</FileVersion>
<Version>10.2.0</Version>
<AssemblyVersion>10.2.0</AssemblyVersion>
<FileVersion>10.2.0</FileVersion>
<PackageIcon>hierarchy_32.png</PackageIcon>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageId>Bitai.LDAPHelper.LdapAdapters.LdapHelperMock</PackageId>
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public bool HasValue(byte[] value) {
}

/// <summary>
/// Returns the base name of the attribute (e.g., "cn" from "cn;lang-ja;phonetic")
/// Returns the base name of the attribute (e.g., "Name" from "Name;lang-ja;phonetic")
/// </summary>
public string GetBaseName() {
return _baseName;
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,22 +1,88 @@
using System.Text;
using Bitai.LDAPHelper.LdapAdapters;
using Bitai.LDAPHelper.LdapAdapters.LdapHelperMock.LdapData;
using Bitai.LDAPHelper.DTO;

namespace Bitai.LDAPHelper.LdapAdapters.LdapHelperMock;

/// <summary>
/// Persistent mock connection backed by a shared in-memory LDAP data store.
/// </summary>
public class MockLdapPersistentConnectionAdapter : MockLdapConnectionAdapter
public class MockLdapPersistentConnectionAdapter : ILdapConnectionAdapter
{
private readonly MockLdapDataStore _dataStore;

public MockLdapPersistentConnectionAdapter() : base()
private bool _disposed = false;
private Dictionary<string, List<MockLdapEntryAdapter>> _searchResults = new();
private bool _isBound = false;

public int ConnectionTimeout { get; set; }
public bool SecureSocketLayer { get; set; }
public bool IsBound => _isBound;
public List<MockLdapEntryAdapter> CreatedEntries { get; } = new List<MockLdapEntryAdapter>();
public List<MockModification> Modifications { get; } = new List<MockModification>();
public List<string> DeletedEntries { get; } = new List<string>();

public MockLdapPersistentConnectionAdapter()
{
_dataStore = MockLdapDataStore.Instance;
}

public override Task AddEntryAsync(string distinguishedName, ILdapAttributeSetAdapter attributes)
public void AddSearchResult(string filterPattern, List<MockLdapEntryAdapter> entries) {
_searchResults[filterPattern] = entries;
}

public void ServerCertificateValidationByPass() {
throw new InvalidOperationException($"{nameof(ServerCertificateValidationByPass)}: Not allowed in mocked classes!");
}

public Task ConnectAsync(string host, int port) {
if (string.IsNullOrEmpty(host) || host == "unknown" || host == "0.0.0.0" || port <= 0)
throw new Exception($"{nameof(MockLdapPersistentConnectionAdapter)}.{nameof(ConnectAsync)}: Invalid server connection!");

return Task.CompletedTask;
}

public Task BindAsync(string userDN, string password) {
if (string.IsNullOrEmpty(userDN) || string.IsNullOrEmpty(password) || userDN.Contains("hacker") || password.Contains("123456"))
throw new LdapOperationException($"{nameof(MockLdapPersistentConnectionAdapter)}.{nameof(BindAsync)}: Invalid credentials!");

if (password.Equals("wrongpassword", StringComparison.OrdinalIgnoreCase))
_isBound = false;
else
_isBound = !string.IsNullOrEmpty(userDN) && !string.IsNullOrEmpty(password);

return Task.CompletedTask;
}

public ILdapAttributeSetAdapter CreateAttributeSet() {
return new MockLdapAttributeSetAdapter();
}

public ILdapModificationAdapter CreateModification(LdapModificationType type, string attributeName, object value) {
return new MockLdapModificationAdapter(type, attributeName, value);
}

public void Disconnect() {
_isBound = false;
}

public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
// Cleanup
}
_disposed = true;
}
}



public Task AddEntryAsync(string distinguishedName, ILdapAttributeSetAdapter attributes)
{
var mockAttributes = (MockLdapAttributeSetAdapter)attributes;
var entry = new MockLdapEntryAdapter(distinguishedName);
Expand Down Expand Up @@ -45,7 +111,7 @@ public override Task AddEntryAsync(string distinguishedName, ILdapAttributeSetAd
return Task.CompletedTask;
}

public override Task ModifyEntryAsync(string distinguishedName, IEnumerable<ILdapModificationAdapter> modifications)
public Task ModifyEntryAsync(string distinguishedName, IEnumerable<ILdapModificationAdapter> modifications)
{
var entry = _dataStore.GetEntry(distinguishedName);
if (entry == null)
Expand Down Expand Up @@ -133,7 +199,7 @@ public override Task ModifyEntryAsync(string distinguishedName, IEnumerable<ILda
return Task.CompletedTask;
}

public override Task DeleteEntryAsync(string distinguishedName)
public Task DeleteEntryAsync(string distinguishedName)
{
if (_dataStore.RemoveEntry(distinguishedName))
{
Expand All @@ -143,7 +209,7 @@ public override Task DeleteEntryAsync(string distinguishedName)
return Task.CompletedTask;
}

public override Task<ILdapSearchQueueAdapter> SearchAsync(
public Task<ILdapSearchQueueAdapter> SearchAsync(
ISearchLimits searchLimits,
string searchFilter,
string[] attributeNames,
Expand All @@ -170,6 +236,8 @@ public override Task<ILdapSearchQueueAdapter> SearchAsync(
return Task.FromResult<ILdapSearchQueueAdapter>(mockQueue);
}



private bool MatchesFilter(MockLdapEntryAdapter entry, string filter)
{
// Simple wildcard matching for demo purposes
Expand All @@ -184,10 +252,10 @@ private bool MatchesFilter(MockLdapEntryAdapter entry, string filter)
var value = ExtractFilterValue(filter, "distinguishedName");
return MatchWildcard(entry.DistinguishedName, value);
}
else if (filter.Contains("(cn="))
else if (filter.Contains("(Name="))
{
var value = ExtractFilterValue(filter, "cn");
var cn = entry.GetAttributeSet().GetAttribute("cn")?.StringValue;
var value = ExtractFilterValue(filter, "Name");
var cn = entry.GetAttributeSet().GetAttribute("Name")?.StringValue;
return MatchWildcard(cn, value);
Comment on lines +255 to 259

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The mock now handles CN filters only when the filter contains (Name=...) and then looks up the case-sensitive Name key, but seeded users and groups still add cn and lowercase name; those entries therefore return no match for name searches in the persistent mock.

Triggers: When the persistent mock is used to search seeded users or groups by the normal LDAP cn attribute.

Suggested fix: Support (cn=...) and read the actual cn attribute, or normalize mock attribute names case-insensitively and consistently seed the same attribute.

}
else if (filter.Contains("(objectSid="))
Expand Down Expand Up @@ -247,6 +315,8 @@ private bool MatchWildcard(string value, string pattern)
return value.Equals(pattern, StringComparison.OrdinalIgnoreCase);
}



private static string ConvertByteToStringSid(byte[] sidBytes)
{
short subAuthorityCount = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Bitai.LDAPHelper.LdapAdapters;
using Bitai.LDAPHelper.LdapAdapters.LdapHelperMock.LdapData;
using Microsoft.Extensions.Logging;

namespace Bitai.LDAPHelper.LdapAdapters.LdapHelperMock;

Expand All @@ -7,11 +9,16 @@ namespace Bitai.LDAPHelper.LdapAdapters.LdapHelperMock;
/// </summary>
public class MockLdapPersistentConnectionFactoryAdapter : ILdapConnectionFactoryAdapter
{
private readonly MockLdapPersistentConnectionAdapter _connection;
//private readonly MockLdapPersistentConnectionAdapter _connection;
private readonly ILogger<MockLdapPersistentConnectionFactoryAdapter> _logger;

public MockLdapPersistentConnectionFactoryAdapter()
public MockLdapPersistentConnectionFactoryAdapter(ILogger<MockLdapPersistentConnectionFactoryAdapter> logger, ILogger<MockLdapDataSeeder> seederLogger)
{
_connection = new MockLdapPersistentConnectionAdapter();
_logger = logger;

var _seeder = new MockLdapDataSeeder(seederLogger);
_seeder.SeedAllData();
_seeder.PrintAllData();
}

public Task<ILdapConnectionAdapter> CreateConnectionAsync(
Expand All @@ -21,7 +28,9 @@ public Task<ILdapConnectionAdapter> CreateConnectionAsync(
bool bindRequired = true)
{
// Always succeed in mock mode
var _connection = new MockLdapPersistentConnectionAdapter();
_connection.BindAsync(userAccount, password);

return Task.FromResult<ILdapConnectionAdapter>(_connection);
}
}
Loading
Loading