Skip to content

[#738] Fix dereferencing an alias that points into another backend#741

Merged
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-738-cross-backend-alias
Jul 16, 2026
Merged

[#738] Fix dereferencing an alias that points into another backend#741
vharseko merged 1 commit into
OpenIdentityPlatform:masterfrom
vharseko:issue-738-cross-backend-alias

Conversation

@vharseko

Copy link
Copy Markdown
Member

Fixes #738.

Cause

LocalBackendSearchOperation resolves the backend once, for the original base DN, and caches it in a field. When the base entry turns out to be an alias, processSearch swaps the base DN and re-enters itself, but never re-resolves the backend, so the recursive call asks the backend of the original base DN for a base DN it does not hold. Aliases whose target lives in the same backend work, which is why this went unnoticed since f771315.

Note that the base entry itself is always found: DirectoryServer.getEntry(baseDN) resolves the backend correctly. Only the search that follows goes to the wrong backend — hence the reported oddity that a base search on the alias target succeeds while a search through the alias does not.

Fix

Re-resolve the backend for the aliased DN before recursing. When no backend holds the target, the backend == null check already at the top of processSearch reports NO_SUCH_OBJECT for the new base DN.

Both backends are also made to honour the LocalBackend.search contract when handed a base DN they do not hold — this is what made the bug so hard to read, since neither returned a usable result code:

  • MemoryBackend only reported a missing base entry when handlesEntry() was true, and otherwise went on to match the filter against a null entry, so a base search answered with a NullPointerException from SearchFilter (result 80) and a subtree search with zero entries.
  • The pluggable backend reported UNDEFINED (-1), a result code used internally.

Both now return NO_SUCH_OBJECT. UNDEFINED stays the default for the other accessBegin() callers, since hasSubordinates() relies on it as a sentinel.

Tests

  • AliasTestCase: a second backend (o=test2), an alias crossing the backend boundary (base and subtree scope), and an alias whose target has no backend at all. Only aliases within o=test were covered so far. All three fail without the fix.
  • PluggableBackendImplTestCase: contract test for a base DN held by no entry container, running for JE, PDB, JDBC and Cassandra.
  • MemoryBackendTestCase: new, the memory backend had no test class of its own.

Also run green: OverlappingBackendTestSuite, LocalBackendWorkflowElementTest, GenericLocalBackendTestCase, LDIFBackendTestCase, SearchOperationTestCase, BackendConfigManagerTestCase, InternalClientConnectionTestCase, InternalSearchOperationTestCase, ChangelogBackendTestCase.

…o another backend

LocalBackendSearchOperation resolves the backend once, for the original base DN,
and caches it in a field. When the base entry turns out to be an alias,
processSearch swaps the base DN and re-enters itself, but never re-resolves the
backend, so the recursive call asks the backend of the original base DN for a
base DN it does not hold. Aliases whose target lives in the same backend work,
which is why this went unnoticed since f771315.

Re-resolve the backend for the aliased DN before recursing. When no backend holds
the target, the null check already at the top of processSearch reports
NO_SUCH_OBJECT for the new base DN.

Make both backends honour the LocalBackend.search contract when handed a base DN
they do not hold, which is what made this bug so hard to read: MemoryBackend only
reported a missing base entry when handlesEntry() was true and otherwise went on
to match the filter against a null entry, answering a base search with a
NullPointerException (result 80); the pluggable backend reported the internally
used UNDEFINED (-1). Both now return NO_SUCH_OBJECT. UNDEFINED stays the default
for the other accessBegin() callers: hasSubordinates() relies on it.

Add AliasTestCase coverage for an alias crossing a backend boundary and for an
alias whose target has no backend at all, a contract test to
PluggableBackendImplTestCase, and MemoryBackendTestCase for the memory backend.
@vharseko vharseko requested a review from maximthomas July 15, 2026 13:42
@vharseko vharseko added bug java Pull requests that update java code labels Jul 15, 2026

@maximthomas maximthomas left a comment

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.

Minor suggestions (non-blocking)

  • Matched-DN on the pluggable path. BackendImpl.search now throws NO_SUCH_OBJECT with no matchedDN, whereas MemoryBackend computes the closest existing
    ancestor. Not a regression (it was UNDEFINED before), but for full LDAP fidelity the pluggable path could populate matchedDN too. Optional / future.
  • Verbosity. DirectoryServer.getInstance().getServerContext().getBackendConfigManager()... is a long chain; if a shorter accessor is reachable from the
    operation, it'd read more cleanly. Cosmetic.
  • Teardown. AliasTestCase.stopServer() cleanly deregisters o=test2 — good hygiene. finalizeBackend() after clearMemoryBackend() is likely redundant
    (finalize clears too) but harmless.
  • Test policy coverage. The added tests all use DereferenceAliasesPolicy.ALWAYS. A cross-backend alias chain (alias → alias in a third backend) and the
    FINDING_BASE policy are only exercised indirectly; an explicit test would lock in that behavior.

@vharseko vharseko merged commit c549b64 into OpenIdentityPlatform:master Jul 16, 2026
17 checks passed
@vharseko vharseko deleted the issue-738-cross-backend-alias branch July 16, 2026 07:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug java Pull requests that update java code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dereferencing an alias that points into another backend searches the wrong backend

2 participants