feat: comprehensive replacement for GetDomain with fallbacks#299
feat: comprehensive replacement for GetDomain with fallbacks#299rvazarkar wants to merge 20 commits intoldap_beta_fixesfrom
Conversation
…of the biggest ADSI gaps, as well as several improvements to the logic
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughIntroduces an asynchronous domain-resolution pipeline and new DomainInfo model; replaces synchronous GetDomain/ADSI lookups with GetDomainInfoAsync calls across LDAP utilities, connection pooling, and processors; adds LdapConfig flags to gate opt-in uncontrolled fallback and introduces case-insensitive caching adjustments. Changes
Sequence Diagram(s)sequenceDiagram
participant Caller as Caller/Processor
participant LdapUtils as LdapUtils
participant PoolMgr as ConnectionPoolManager/Pool
participant DirectLDAP as Direct LDAP Bind
participant ADSI as ADSI DirectoryEntry
participant Uncontrolled as Domain.GetDomain (opt-in)
Caller->>LdapUtils: GetDomainInfoAsync(domain?)
alt Pooled controlled LDAP available
LdapUtils->>PoolMgr: Use pooled async search
PoolMgr-->>LdapUtils: (Success, DomainInfo)
else try direct LDAP bind
LdapUtils->>DirectLDAP: One-shot async LDAP bind
DirectLDAP-->>LdapUtils: (Success, DomainInfo)
else try ADSI fallback
LdapUtils->>ADSI: DirectoryEntry lookup
ADSI-->>LdapUtils: (Success, DomainInfo)
else uncontrolled fallback allowed
LdapUtils->>Uncontrolled: Domain.GetDomain()
Uncontrolled-->>LdapUtils: (Success, DomainInfo)
end
LdapUtils-->>Caller: (Success, DomainInfo)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
test/unit/Facades/MockLdapUtils.cs (1)
706-712: Consider returning a baselineDomainInfofrom the test double.This mock already models a default
TESTLAB.LOCALenvironment viaGetForest,GetDomainSidFromDomainName, and the identity resolvers. Returning(false, null)here makes the new API the only domain-resolution path that fails by default, which can turn unrelated tests red as production code migrates fromGetDomain*toGetDomainInfoAsync(). A minimal happy-pathDomainInfowould keep the base fixture behavior consistent.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/unit/Facades/MockLdapUtils.cs` around lines 706 - 712, The mock's GetDomainInfoAsync overloads return (false, null) which breaks the established TESTLAB.LOCAL baseline used by GetForest and GetDomainSidFromDomainName; update both GetDomainInfoAsync(string) and parameterless GetDomainInfoAsync() to return Task.FromResult((true, baselineDomainInfo)) where baselineDomainInfo is a minimal DomainInfo instance that matches the existing test fixture (e.g., DomainName "TESTLAB.LOCAL", the same ForestName and DomainSid used by GetForest/GetDomainSidFromDomainName and any identity resolvers) so tests continue to see a successful, consistent domain resolution path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/CommonLib/DomainInfo.cs`:
- Around line 12-33: DomainInfo instances are mutable and are being returned
from a static cache in LdapUtils.cs, allowing callers to mutate shared cached
objects; make DomainInfo immutable (remove public setters on Name,
DistinguishedName, ForestName, DomainSid, NetBiosName, PrimaryDomainController
and DomainControllers) and provide a single constructor (or init-only
properties) that fully initializes all properties (keeping DomainControllers as
an IReadOnlyList<string> defaulting to Array.Empty<string>()), then update
LdapUtils.cs to only store and return immutable DomainInfo objects (or to
clone/create a new DomainInfo instance at the cache boundary when
reading/writing) so cached state cannot be mutated by consumers.
In `@src/CommonLib/LdapUtils.cs`:
- Around line 35-36: The static cache _domainInfoCache currently keys only by
domain and can return entries produced by
TryGetDomainInfoViaUncontrolledFallback even when
LdapConfig.AllowFallbackToUncontrolledLdap is false; fix by encoding fallback
provenance in the cache key or marking cached DomainInfo entries with a boolean
(e.g., DomainInfo.IsFallback) and, at every cache read site (places that read
_domainInfoCache and return cached values, and where
TryGetDomainInfoViaUncontrolledFallback writes into the cache), revalidate
against LdapConfig.AllowFallbackToUncontrolledLdap—only return a cached
IsFallback entry when AllowFallbackToUncontrolledLdap is true, otherwise bypass
the cached fallback entry and proceed to controlled lookup or refresh the cache
entry appropriately.
- Around line 1456-1457: The XML doc comments in LdapUtils.cs reference a
non-existent member DomainInfo.FromFallback which causes unresolved cref
warnings; fix by either adding a boolean member/property named FromFallback to
the DomainInfo class (and document it) or update the XML docs to remove or
replace the cref with an existing member (e.g., an actual property like
DomainInfo.IsFallback or simply remove the cref). Update all occurrences that
reference DomainInfo.FromFallback so the cref targets a real symbol or is
removed to eliminate the unresolved-reference warnings.
---
Nitpick comments:
In `@test/unit/Facades/MockLdapUtils.cs`:
- Around line 706-712: The mock's GetDomainInfoAsync overloads return (false,
null) which breaks the established TESTLAB.LOCAL baseline used by GetForest and
GetDomainSidFromDomainName; update both GetDomainInfoAsync(string) and
parameterless GetDomainInfoAsync() to return Task.FromResult((true,
baselineDomainInfo)) where baselineDomainInfo is a minimal DomainInfo instance
that matches the existing test fixture (e.g., DomainName "TESTLAB.LOCAL", the
same ForestName and DomainSid used by GetForest/GetDomainSidFromDomainName and
any identity resolvers) so tests continue to see a successful, consistent domain
resolution path.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 21e5791e-c2db-494d-afb2-12809aeda7a6
📒 Files selected for processing (11)
src/CommonLib/ConnectionPoolManager.cssrc/CommonLib/DomainInfo.cssrc/CommonLib/Enums/LDAPProperties.cssrc/CommonLib/ILdapUtils.cssrc/CommonLib/LdapConfig.cssrc/CommonLib/LdapConnectionPool.cssrc/CommonLib/LdapUtils.cssrc/CommonLib/Processors/GPOLocalGroupProcessor.cstest/unit/Facades/MockLdapUtils.cstest/unit/GPOLocalGroupProcessorTest.cstest/unit/LDAPUtilsTest.cs
| /// <see cref="DomainInfo"/> with <see cref="DomainInfo.FromFallback"/> = false. A failure | ||
| /// to acquire the connection or to read the default NC is fatal and returns <c>(false, null)</c>. |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd -e csproj -e props -e targets | xargs -r rg -n "GenerateDocumentationFile|TreatWarningsAsErrors|WarningsAsErrors"
rg -n "DomainInfo\\.FromFallback|<see cref=\"DomainInfo\\.FromFallback\""Repository: SpecterOps/SharpHoundCommon
Length of output: 332
🏁 Script executed:
fd -e cs -i "*domaininfo*" | head -20Repository: SpecterOps/SharpHoundCommon
Length of output: 362
🏁 Script executed:
cat -n src/CommonLib/DomainInfo.cs | head -100Repository: SpecterOps/SharpHoundCommon
Length of output: 1916
🏁 Script executed:
rg -n "FromFallback" src/CommonLib/Repository: SpecterOps/SharpHoundCommon
Length of output: 332
The XML docs reference a member that doesn't exist.
Both comments at lines 1456 and 1630 point to DomainInfo.FromFallback, but the DomainInfo class has no such member. These broken crefs will raise unresolved-reference warnings when XML docs are generated. Either add the missing member or remove the cref references.
Also applies to: 1629-1630
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/CommonLib/LdapUtils.cs` around lines 1456 - 1457, The XML doc comments in
LdapUtils.cs reference a non-existent member DomainInfo.FromFallback which
causes unresolved cref warnings; fix by either adding a boolean member/property
named FromFallback to the DomainInfo class (and document it) or update the XML
docs to remove or replace the cref with an existing member (e.g., an actual
property like DomainInfo.IsFallback or simply remove the cref). Update all
occurrences that reference DomainInfo.FromFallback so the cref targets a real
symbol or is removed to eliminate the unresolved-reference warnings.
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (2)
test/unit/GPOLocalGroupProcessorTest.cs (1)
326-327: Add coverage for the new failure path.This only locks in the
(true, DomainInfo)branch. The new early return insrc/CommonLib/Processors/GPOLocalGroupProcessor.csat Line 70 is still untested whenGetDomainInfoAsync()fails or returns an emptyName.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@test/unit/GPOLocalGroupProcessorTest.cs` around lines 326 - 327, Add a unit test in GPOLocalGroupProcessorTest that exercises the new early-return failure path in GPOLocalGroupProcessor: mock LDAPUtils.GetDomainInfoAsync() to return failure (e.g., (false, null)) and a separate case where it returns (true, new DomainInfo{name = ""}) or DomainInfo with an empty Name, then call the GPOLocalGroupProcessor.Process* method (the method under test) and assert the processor returns early/does not proceed (verify no further LDAP calls, no changes, or expected return value). Use the same mock setup pattern as the existing test (mockLDAPUtils.Setup(x => x.GetDomainInfoAsync()).ReturnsAsync(...)) and assert expectations on the processor behavior to cover both failure and empty-name branches referenced by GPOLocalGroupProcessor and DomainInfo.src/CommonLib/ILdapUtils.cs (1)
92-109: Clarify the caching semantics in these XML docs.The wording here reads like disabling
AllowFallbackToUncontrolledLdapguarantees no fallback-derivedDomainInfocan be observed. In this PR, cached entries populated earlier can still be returned with the flag off because that path performs no network I/O, so the docs should call that out explicitly.Based on learnings,
LdapConfig.AllowFallbackToUncontrolledLdapis intentionally a gate on network activity only; cached_domainInfoCacheentries may still be returned regardless of the current flag setting because serving cache performs no network I/O.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/CommonLib/ILdapUtils.cs` around lines 92 - 109, Update the XML documentation on both GetDomainInfoAsync(string) and GetDomainInfoAsync() to explicitly state that LdapConfig.AllowFallbackToUncontrolledLdap only controls whether network-based fallback (System.DirectoryServices.ActiveDirectory.Domain.GetDomain) is attempted, and does not prevent returning previously cached DomainInfo from the internal _domainInfoCache; clarify that cache hits may be returned even when the flag is false because serving from cache performs no network I/O. Mention both the flag name (LdapConfig.AllowFallbackToUncontrolledLdap) and the cache field name (_domainInfoCache) so readers can locate the related logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/CommonLib/ConnectionPoolManager.cs`:
- Around line 147-159: The legacy ADSI bind (Helpers.CreateDirectoryEntry) is
still invoked before using the new helper; update the flow so you call
LdapUtils.GetDomainInfoStaticAsync(domainName, _ldapConfig, _log) and inspect
the returned info (DomainSid or availability) before calling
Helpers.CreateDirectoryEntry($"LDAP://{domainName}", _ldapConfig). If
GetDomainInfoStaticAsync returns useful info (infoOk &&
!string.IsNullOrEmpty(info?.DomainSid)) short-circuit and use
Cache.AddDomainSidMapping/return, otherwise fall back to the existing
Helpers.CreateDirectoryEntry path; this prevents uncontrolled/serverless ADSI
lookups from running prior to
ResolveIdentifier/GetPool/GetLdapConnectionForServer logic.
In `@src/CommonLib/ILdapUtils.cs`:
- Around line 92-109: The added methods GetDomainInfoAsync(string) and
GetDomainInfoAsync() on ILdapUtils are source-breaking; instead of modifying
ILdapUtils, create a new interface (e.g. IDomainInfoResolver or
ILdapDomainResolver) that declares Task<(bool Success, DomainInfo DomainInfo)>
GetDomainInfoAsync(string) and Task<(bool Success, DomainInfo DomainInfo)>
GetDomainInfoAsync(), implement that new interface in the classes that provide
domain resolution, and update callers that need this functionality to depend on
the new interface rather than changing the existing ILdapUtils contract.
In `@src/CommonLib/LdapConnectionPool.cs`:
- Around line 917-925: The foreach over info.DomainControllers in
LdapConnectionPool (strategy 6) can throw when GetDomainInfoStaticAsync returns
a partial DomainInfo without DomainControllers; guard this path by checking that
info.DomainControllers is not null and has any entries before iterating, and if
empty/null skip strategy 6 (log a debug/info message) instead of allowing the
exception to bubble; update the block around CreateLDAPConnectionWithPortCheck
and the return path accordingly so only valid controllers are attempted.
In `@src/CommonLib/LdapUtils.cs`:
- Around line 1410-1425: The Domain instance returned by Domain.GetDomain
(stored in the local variable domain) is not disposed; update
TryResolveHintViaUncontrolledGetDomain so that after you read domain.Name you
dispose the Domain object (e.g., wrap Domain.GetDomain and the access of
domain.Name in a using or ensure domain.Dispose() in a finally) before
returning; preserve setting _uncontrolledGetDomainHint and domainName from name
and keep the existing catch/log behavior for exceptions.
- Around line 1670-1760: The Domain returned by Domain.GetDomain(context) and
the DirectoryEntry returned by domain.GetDirectoryEntry() are IDisposable and
must be disposed to avoid native handle leaks; wrap the Domain usage (the
variable domain) in a using (or try/finally with domain?.Dispose()) around all
accesses (forest, PdcRoleOwner, DomainControllers, GetDirectoryEntry) and
similarly ensure the rawEntry (and any DirectoryEntry-derived object from
rawEntry.ToDirectoryObject()) is disposed after extracting domainSid (use using
or finally on rawEntry and the converted entry); preserve the existing try/catch
behavior and the order of operations but add disposal for Domain and
DirectoryEntry objects (referencing Domain.GetDomain(context),
domain.GetDirectoryEntry(), rawEntry and the entry produced by
ToDirectoryObject()).
---
Nitpick comments:
In `@src/CommonLib/ILdapUtils.cs`:
- Around line 92-109: Update the XML documentation on both
GetDomainInfoAsync(string) and GetDomainInfoAsync() to explicitly state that
LdapConfig.AllowFallbackToUncontrolledLdap only controls whether network-based
fallback (System.DirectoryServices.ActiveDirectory.Domain.GetDomain) is
attempted, and does not prevent returning previously cached DomainInfo from the
internal _domainInfoCache; clarify that cache hits may be returned even when the
flag is false because serving from cache performs no network I/O. Mention both
the flag name (LdapConfig.AllowFallbackToUncontrolledLdap) and the cache field
name (_domainInfoCache) so readers can locate the related logic.
In `@test/unit/GPOLocalGroupProcessorTest.cs`:
- Around line 326-327: Add a unit test in GPOLocalGroupProcessorTest that
exercises the new early-return failure path in GPOLocalGroupProcessor: mock
LDAPUtils.GetDomainInfoAsync() to return failure (e.g., (false, null)) and a
separate case where it returns (true, new DomainInfo{name = ""}) or DomainInfo
with an empty Name, then call the GPOLocalGroupProcessor.Process* method (the
method under test) and assert the processor returns early/does not proceed
(verify no further LDAP calls, no changes, or expected return value). Use the
same mock setup pattern as the existing test (mockLDAPUtils.Setup(x =>
x.GetDomainInfoAsync()).ReturnsAsync(...)) and assert expectations on the
processor behavior to cover both failure and empty-name branches referenced by
GPOLocalGroupProcessor and DomainInfo.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: ae63858a-1177-481c-815f-e4e52182c227
📒 Files selected for processing (11)
src/CommonLib/ConnectionPoolManager.cssrc/CommonLib/DomainInfo.cssrc/CommonLib/Enums/LDAPProperties.cssrc/CommonLib/ILdapUtils.cssrc/CommonLib/LdapConfig.cssrc/CommonLib/LdapConnectionPool.cssrc/CommonLib/LdapUtils.cssrc/CommonLib/Processors/GPOLocalGroupProcessor.cstest/unit/Facades/MockLdapUtils.cstest/unit/GPOLocalGroupProcessorTest.cstest/unit/LDAPUtilsTest.cs
| /// <summary> | ||
| /// Resolves a <see cref="DomainInfo"/> for the specified domain using controlled LDAP queries | ||
| /// that honor the configured <see cref="LdapConfig"/> (server, port, SSL, auth, signing, cert verification). | ||
| /// Falls back to <c>System.DirectoryServices.ActiveDirectory.Domain.GetDomain</c> only when | ||
| /// <see cref="LdapConfig.AllowFallbackToUncontrolledLdap"/> is enabled. | ||
| /// </summary> | ||
| /// <param name="domainName">The domain name to resolve</param> | ||
| /// <returns>A tuple containing success state as well as the populated DomainInfo if successful</returns> | ||
| Task<(bool Success, DomainInfo DomainInfo)> GetDomainInfoAsync(string domainName); | ||
|
|
||
| /// <summary> | ||
| /// Resolves a <see cref="DomainInfo"/> for the user's current domain using controlled LDAP queries | ||
| /// that honor the configured <see cref="LdapConfig"/>. Falls back to | ||
| /// <c>System.DirectoryServices.ActiveDirectory.Domain.GetDomain</c> only when | ||
| /// <see cref="LdapConfig.AllowFallbackToUncontrolledLdap"/> is enabled. | ||
| /// </summary> | ||
| /// <returns>A tuple containing success state as well as the populated DomainInfo if successful</returns> | ||
| Task<(bool Success, DomainInfo DomainInfo)> GetDomainInfoAsync(); |
There was a problem hiding this comment.
This public interface change is source-breaking.
Adding two required members to ILdapUtils will break any downstream implementation at compile time, so this cannot be treated as a non-breaking feature. If compatibility matters, introduce a new interface or adapter for domain-info resolution instead of extending the existing public contract.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/CommonLib/ILdapUtils.cs` around lines 92 - 109, The added methods
GetDomainInfoAsync(string) and GetDomainInfoAsync() on ILdapUtils are
source-breaking; instead of modifying ILdapUtils, create a new interface (e.g.
IDomainInfoResolver or ILdapDomainResolver) that declares Task<(bool Success,
DomainInfo DomainInfo)> GetDomainInfoAsync(string) and Task<(bool Success,
DomainInfo DomainInfo)> GetDomainInfoAsync(), implement that new interface in
the classes that provide domain resolution, and update callers that need this
functionality to depend on the new interface rather than changing the existing
ILdapUtils contract.
fix: ensure all paths respect the ldapconfig.server configuration
…d to cache misses fix: domain/sid mapping properly caches in both directions
fix: set null cache key before writes to ensure conflicts are resolved properly
…he cache with netbios names
…nd other caching will take care of it
fix: invalid cached path fix: DCInfoCache case sensitivity fix: a race condition for domain info resolution that could result in multiple callers attempting to resolve the same domain, use lazy loading instead
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/CommonLib/Cache.cs`:
- Around line 175-192: NormalizeCaseInsensitiveCaches() will throw if the
persisted source dictionaries contain keys that collide under
StringComparer.OrdinalIgnoreCase; instead of calling the ConcurrentDictionary
constructor with the entire source, create new ConcurrentDictionary instances
for SIDToDomainCache, GlobalCatalogCache, and ValueToIdCache with
StringComparer.OrdinalIgnoreCase and copy entries into them entry-by-entry using
TryAdd (or TryAdd-equivalent) so duplicate keys differing only by case are
ignored and the cache is "healed" rather than crashing; update the code paths
that assign CacheInstance.SIDToDomainCache, CacheInstance.GlobalCatalogCache,
and CacheInstance.ValueToIdCache to perform this safe per-entry copy.
In `@src/CommonLib/LdapUtils.cs`:
- Around line 44-49: The domain hint caches (_uncontrolledGetDomainHint and
_currentDomain) are not invalidated when LDAP credentials/config change; update
SetLdapConfig() to clear or re-key those caches whenever CurrentUserDomain,
Username, Password or other auth-related config is set (in addition to
rebuilding _connectionPool), so the methods ResolveEffectiveDomainHint and any
callers of _currentDomain won't return stale values; alternatively implement
cache entries keyed by the current config (e.g., include a config fingerprint)
and ensure ResetUtils() still clears them.
- Around line 36-37: The static cache _domainInfoCache is poisoned when
TryResolveDomainInfoViaDirectLdapAsync returns a DomainInfo whose Name differs
from the requested domainName (because config.Server overrides bind target);
update caching so entries are keyed by the effective bind target/config or by
the resolved canonical name instead of blindly caching under the requested
domainName: modify TryResolveDomainInfoViaDirectLdapAsync, GetDomainInfoAsync
and GetDomainInfoStaticAsync to compute a cache key that includes either the
resolved DomainInfo.Name or the effective LDAP server/credential identity (e.g.,
combine config.Server or resolved.Name with domainName), and only insert into
_domainInfoCache under that computed key — if resolved.Name != requested
domainName, do not cache under the original request key (or also add a separate
entry keyed by resolved.Name) so callers without overrides cannot read incorrect
SID/DN/forest from cache.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f10704a7-60ce-4e57-bdd6-4c4cec85dfc2
📒 Files selected for processing (7)
src/CommonLib/Cache.cssrc/CommonLib/ConnectionPoolManager.cssrc/CommonLib/LdapConnectionPool.cssrc/CommonLib/LdapUtils.cssrc/CommonLib/Processors/GPOLocalGroupProcessor.cstest/unit/CacheTest.cstest/unit/LDAPUtilsTest.cs
🚧 Files skipped from review as they are similar to previous changes (1)
- src/CommonLib/Processors/GPOLocalGroupProcessor.cs
| private static ConcurrentDictionary<string, DomainInfo> _domainInfoCache = | ||
| new(StringComparer.OrdinalIgnoreCase); |
There was a problem hiding this comment.
_domainInfoCache can be poisoned by LdapConfig.Server overrides.
TryResolveDomainInfoViaDirectLdapAsync() explicitly allows the resolved DomainInfo.Name to differ from the requested domainName when config.Server points at a DC in another domain, but both GetDomainInfoAsync() and GetDomainInfoStaticAsync() still cache that record under the requested key. Because _domainInfoCache is static and keyed only by domain string, a later caller without that override can read the wrong SID / distinguished name / forest from cache. Either scope the cache by effective bind target/config, or skip caching under the request key when the canonical name disagrees.
Also applies to: 1313-1448, 2147-2152
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/CommonLib/LdapUtils.cs` around lines 36 - 37, The static cache
_domainInfoCache is poisoned when TryResolveDomainInfoViaDirectLdapAsync returns
a DomainInfo whose Name differs from the requested domainName (because
config.Server overrides bind target); update caching so entries are keyed by the
effective bind target/config or by the resolved canonical name instead of
blindly caching under the requested domainName: modify
TryResolveDomainInfoViaDirectLdapAsync, GetDomainInfoAsync and
GetDomainInfoStaticAsync to compute a cache key that includes either the
resolved DomainInfo.Name or the effective LDAP server/credential identity (e.g.,
combine config.Server or resolved.Name with domainName), and only insert into
_domainInfoCache under that computed key — if resolved.Name != requested
domainName, do not cache under the original request key (or also add a separate
entry keyed by resolved.Name) so callers without overrides cannot read incorrect
SID/DN/forest from cache.
fix: reset currentDomain when changing LDAPConfig
… the logic fix: reset DCInfoCache when utils are reset
…NS name to avoid cache poisoning
… match our expected precedence
Description
Addresses one of the main offenders of uncontrolled LDAP calls in the codebase. Also fixes several gaps in netonly scenarios, and drastically improves domain info resolution with several fallbacks. Adds an optional flag to the LDAPConfig which allows
Motivation and Context
This PR addresses: [GitHub issue or Jira ticket number]
How Has This Been Tested?
Unit tests for many of the changes, real testing to follow.
Screenshots (if appropriate):
Types of changes
Checklist:
Summary by CodeRabbit
New Features
Refactor
Bug Fixes
Tests