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
100 changes: 98 additions & 2 deletions dirsrvtests/tests/suites/basic/ds_entrydn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@
import logging
import os
import pytest
from lib389.backend import Backends
from lib389.config import BDB_LDBMConfig, LMDB_LDBMConfig
from lib389.dbgen import dbgen_users, get_index
from lib389.idm.organizationalunit import OrganizationalUnit
from lib389.idm.user import UserAccount, UserAccounts
from lib389.tasks import ImportTask
from lib389.utils import get_default_db_lib
from test389.topologies import topology_st as topo

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -40,6 +43,26 @@ def _import_user_dns():
]


@pytest.fixture
def export_cache_setup(topo, request):
inst = topo.standalone
export_file = os.path.join(inst.get_ldif_dir(), 'dsentrydn-export.ldif')
backend = Backends(inst).get('userRoot')
config_ldbm = (BDB_LDBMConfig(inst) if get_default_db_lib() == 'bdb'
else LMDB_LDBMConfig(inst))
old_autosize = config_ldbm.get_attr_val_utf8('nsslapd-cache-autosize')
old_cache_size = backend.get_attr_val_utf8('nsslapd-cachesize')

def cleanup():
backend.replace('nsslapd-cachesize', old_cache_size)
config_ldbm.replace('nsslapd-cache-autosize', old_autosize)
if os.path.exists(export_file):
os.remove(export_file)

request.addfinalizer(cleanup)
return backend, config_ldbm, export_file


def test_dsentrydn_preserved_on_modify(topo):
"""Test that dsEntryDN is not corrupted when an entry is modified

Expand Down Expand Up @@ -105,6 +128,12 @@ def test_dsentrydn_preserved_on_modify(topo):
assert current_dsentrydn == orig_dsentrydn, \
f"dsEntryDN was corrupted: expected '{orig_dsentrydn}' but got '{current_dsentrydn}'"

users = UserAccounts(inst, SUFFIX).list()
matching_users = [entry for entry in users
if entry.get_attr_val_utf8('uid') == 'modUser']
assert len(matching_users) == 1
assert matching_users[0].dn == orig_dsentrydn


def test_dsentrydn_case_only_rename(topo):
"""Test that dsEntryDN is updated on a case-only MODRDN
Expand All @@ -128,8 +157,7 @@ def test_dsentrydn_case_only_rename(topo):
inst = topo.standalone
inst.config.replace('nsslapd-return-original-entrydn', 'on')

users = UserAccounts(inst, SUFFIX)
user = users.create(properties={
user = UserAccount(inst, f'uid=caseRenameUser,ou=People,{SUFFIX}').create(properties={
'uid': 'caseRenameUser',
'givenname': 'Case',
'cn': 'Case Rename User',
Expand Down Expand Up @@ -221,6 +249,74 @@ def test_dsentrydn(topo):
break


def test_prefer_dsentrydn_over_cached_dn(topo, export_cache_setup):
"""Returned DN must use dsEntryDN after backend export populates the DN cache

:id: 2f6e4b8a-1c73-4d95-a0e2-8b6f3c1d7a54
:setup: Standalone Instance
:steps:
1. Enable nsslapd-return-original-entrydn
2. Create an entry with mixed-case DN components
3. Restart the instance to clear runtime caches
4. Export the backend with replication data
5. Search for the entry after export
:expectedresults:
1. Success
2. dsEntryDN stores the original-form DN
3. Success
4. Export completes successfully
5. Returned DN matches dsEntryDN:
uid=exportCacheUser,dc=Example,DC=COM
dsEntryDN: uid=exportCacheUser,dc=Example,DC=COM
"""
inst = topo.standalone
inst.config.replace('nsslapd-return-original-entrydn', 'on')

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 test enables nsslapd-return-original-entrydn but never restores its previous value, so the shared standalone topology remains configured to return original-form DNs after the test finishes. Subsequent tests that expect the default normalized-DN behavior become order-dependent and can fail or silently exercise a different configuration.

Triggers: When this test runs before other tests using the same topology.

Suggested fix: Save the original setting and restore it in the fixture finalizer, or add the setting restoration to export_cache_setup cleanup.


user = UserAccount(inst, f'uid=exportCacheUser,{SUFFIX}').create(properties={
'uid': 'exportCacheUser',
'givenname': 'Export Cache',
'cn': 'Export Cache User',
'sn': 'User',
'userpassword': 'password',
'uidNumber': '1003',
'gidNumber': '1003',
'homeDirectory': '/home/exportCacheUser'
})
original_dn = user.get_attr_val_utf8('dsentrydn')
expected_original_dn = f'uid=exportCacheUser,{SUFFIX}'
assert original_dn == expected_original_dn
second_user = UserAccount(inst, f'uid=exportCacheUser2,{SUFFIX}').create(properties={
'uid': 'exportCacheUser2',
'givenname': 'Export Cache Two',
'cn': 'Export Cache User Two',
'sn': 'User',
'userpassword': 'password',
'uidNumber': '1004',
'gidNumber': '1004',
'homeDirectory': '/home/exportCacheUser2'
})

inst.restart()
backend, config_ldbm, export_file = export_cache_setup
export_task = Backends(inst).export_ldif(
be_names='userRoot', ldif=export_file, replication=True)
export_task.wait()

# Force entrycache miss with DN cache hit to accelerate the reproducer
inst.config.replace('nsslapd-return-original-entrydn', 'off')
UserAccount(inst, original_dn).search(scope='base', filter='objectclass=*')
config_ldbm.replace('nsslapd-cache-autosize', '0')
backend.replace('nsslapd-cachesize', '1')
UserAccount(inst, second_user.dn).search(scope='base', filter='objectclass=*')
inst.config.replace('nsslapd-return-original-entrydn', 'on')

returned_dn = UserAccount(inst, original_dn).search(
scope='base', filter='objectclass=*')[0].dn
assert returned_dn == original_dn, \
f"returned DN '{returned_dn}' differs from dsEntryDN '{original_dn}' " \
f"(normalized form would be 'uid=exportCacheUser,dc=example,dc=com')"


def test_dsentrydn_import_ldif(topo, request):
"""Imported entries receive dsEntryDN matching their DN

Expand Down
10 changes: 8 additions & 2 deletions ldap/servers/slapd/back-ldbm/id2entry.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,13 @@ id2entry(backend *be, ID id, back_txn *txn, int *err)
Slapi_RDN *srdn = NULL;
struct backdn *bdn = dncache_find_id(&inst->inst_dncache, id);
if (bdn) {
normdn = slapi_ch_strdup(slapi_sdn_get_dn(bdn->dn_sdn));
if (config_get_return_orig_dn() &&
!get_value_from_string((const char *)data.dptr, SLAPI_ATTR_DS_ENTRYDN, &normdn))
{
srdn = slapi_rdn_new_all_dn(normdn);
} else {
normdn = slapi_ch_strdup(slapi_sdn_get_dn(bdn->dn_sdn));
}
slapi_log_err(SLAPI_LOG_CACHE, ID2ENTRY,
"dncache_find_id returned: %s\n", normdn);
CACHE_RETURN(&inst->inst_dncache, &bdn);
Expand Down Expand Up @@ -389,7 +395,7 @@ id2entry(backend *be, ID id, back_txn *txn, int *err)
}
}

sdn = slapi_sdn_new_normdn_byval((const char *)normdn);
sdn = slapi_sdn_new_dn_byval((const char *)normdn);
bdn = backdn_init(sdn, id, 0);
if (CACHE_ADD(&inst->inst_dncache, bdn, NULL)) {
backdn_free(&bdn);
Expand Down
Loading