From b70343dc0c45567fbf526557f41040e023b9dafa Mon Sep 17 00:00:00 2001 From: Viktor Ashirov Date: Fri, 20 Feb 2026 14:34:11 +0100 Subject: [PATCH] Bump version to 3.2.0-3 - Resolves: RHEL-76835 - Web console doesn't show the sub suffix of ou=foo,ou=people,dc=example,dc=com. - Resolves: RHEL-86320 - [RFE] Support updating/renewing TLS certificate without restarting slapd - Resolves: RHEL-110192 - ns-slapd doesn't support PQC keys - Resolves: RHEL-111220 - RHDS-11.9 dsctl db2index --attr recreates all indexes instead of selected ones - Resolves: RHEL-111931 - Improve error messages for dsconf localpwp list - Resolves: RHEL-121981 - Setting password history count to 0 does not flush history - Resolves: RHEL-122625 - ipa-healthcheck is complaining about missing or incorrectly configured system indexes. - Resolves: RHEL-128906 - Scalability issue of replication online initialization with large database - Resolves: RHEL-137786 - Upgrading IDM to latest version: 389-ds-base and ipa-server breaks replication - Resolves: RHEL-146769 - Remove memberof_del_dn_from_groups from MemberOf plugin --- ...Issue-6753-Port-ticket-548-test-7101.patch | 924 +++++ ...apd-fails-to-shutdown-when-deferred-.patch | 82 + ...utomember_plugin-CI-test-failures-71.patch | 61 + ...UIA-selectors-for-WebUI-plugin-tests.patch | 115 + ...icCertificates-returns-empty-DER-719.patch | 32 + ...0007-generates-incorrect-remediation.patch | 235 ++ ...-Issue-7170-Support-of-PQC-keys-7188.patch | 504 +++ ...-4.17.21-to-4.17.23-in-src-cockpit-3.patch | 56 + ...onsole-doesn-t-show-sub-suffix-when-.patch | 517 +++ ...rOf-ignored-deferred-updates-with-LM.patch | 38 + ...rse.HelpFormatter-_format_actions_us.patch | 48 + ...e-time-skew-check-in-healthcheck-too.patch | 174 + ...ll-overhead-in-LMDB-import-writer-th.patch | 215 ++ ...uring-replication-online-total-init-.patch | 135 + ...d-log-whether-TLS-key-is-PQC-or-not-.patch | 149 + ...89-ds-base-OpenScanHub-Leaks-Detecte.patch | 53 + ...AD_VALSIZE-error-while-handling-VLV-.patch | 195 ++ ...sue-6753-Port-ticket-47781-test-7210.patch | 285 ++ ...Log-Analysis-Add-CSN-propagation-det.patch | 1645 +++++++++ 0025-Issue-6753-Port-ticket-48896-test.patch | 265 ++ 0026-Issue-6810-Fix-PAM-PTA-test-7219.patch | 30 + ...evert_cache-never-called-in-modrdn-7.patch | 57 + ...ic-Certificate-refresh-phase-4-Updat.patch | 1492 ++++++++ ...st-Simplify-test_reserve_descriptor_.patch | 93 + ...ed-jemalloc-fails-to-build-with-GCC-.patch | 94 + ...eakSanitizer-various-leaks-during-re.patch | 71 + ...t-index-scan-limits-for-system-index.patch | 778 +++++ ...pgrade-function-to-remove-nsIndexIDL.patch | 212 ++ ...pgrade-function-to-remove-ancestorid.patch | 313 ++ ...t-and-log-index-ordering-mismatch-du.patch | 300 ++ ...sctl-index-check-command-for-offline.patch | 1233 +++++++ ...gression-in-healtcheck-NssCheck-7235.patch | 62 + ...x-audit-issue-with-npm-isaacs-brace-.patch | 1225 +++++++ ...e-7221-CI-tests-fix-some-flaky-tests.patch | 97 + ...produce_division_by_zero-fails-with-.patch | 56 + 0041-Issue-7241-Drop-dateutil-7242.patch | 226 ++ ...repl-tests-fail-in-FIPS-mode-due-to-.patch | 94 + ...ttribute-uniqueness-fix-usage-for-ex.patch | 33 + ...db2index-needs-some-hardening-with-M.patch | 201 ++ ...eed-to-iterate-on-SECOidTag-instead-.patch | 74 + ...mic-Certificas-Refresh-CI-tests-7238.patch | 3031 +++++++++++++++++ ...rgparse.HelpFormatter-_format_action.patch | 41 + ...DB_BAD_VALSIZE-error-while-handling-.patch | 32 + ...exicographical-order-for-ancestorid-.patch | 34 + 0050-Issue-3134-Fix-build-break-7260.patch | 38 + ...allow-password-history-to-be-set-to-.patch | 87 + ...-UI-add-support-for-hot-certificates.patch | 1379 ++++++++ ...nable-Replication-dropdown-not-openi.patch | 32 + ...e-integerOrderingMatch-requirement-f.patch | 538 +++ 0055-Issue-7236-Fix-GSSAPI-tests-7237.patch | 227 ++ 0056-Issue-6753-Port-ticket-49039-test.patch | 260 ++ 389-ds-base.spec | 51 + 52 files changed, 18219 insertions(+) create mode 100644 0006-Issue-6753-Port-ticket-548-test-7101.patch create mode 100644 0007-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch create mode 100644 0008-Issue-7169-Fix-automember_plugin-CI-test-failures-71.patch create mode 100644 0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch create mode 100644 0010-Issue-7196-DynamicCertificates-returns-empty-DER-719.patch create mode 100644 0011-Issue-7189-DSBLE0007-generates-incorrect-remediation.patch create mode 100644 0012-Issue-7170-Support-of-PQC-keys-7188.patch create mode 100644 0013-Bump-lodash-from-4.17.21-to-4.17.23-in-src-cockpit-3.patch create mode 100644 0014-Issue-7198-Web-console-doesn-t-show-sub-suffix-when-.patch create mode 100644 0015-Issue-7014-memberOf-ignored-deferred-updates-with-LM.patch create mode 100644 0016-Issue-7184-argparse.HelpFormatter-_format_actions_us.patch create mode 100644 0017-Issue-6947-Revise-time-skew-check-in-healthcheck-too.patch create mode 100644 0018-Issue-7201-Syscall-overhead-in-LMDB-import-writer-th.patch create mode 100644 0019-Issue-7096-2nd-During-replication-online-total-init-.patch create mode 100644 0020-Issue-7206-Should-log-whether-TLS-key-is-PQC-or-not-.patch create mode 100644 0021-Issue-7027-2nd-389-ds-base-OpenScanHub-Leaks-Detecte.patch create mode 100644 0022-Issue-7213-MDB_BAD_VALSIZE-error-while-handling-VLV-.patch create mode 100644 0023-Issue-6753-Port-ticket-47781-test-7210.patch create mode 100644 0024-Issue-7194-Repl-Log-Analysis-Add-CSN-propagation-det.patch create mode 100644 0025-Issue-6753-Port-ticket-48896-test.patch create mode 100644 0026-Issue-6810-Fix-PAM-PTA-test-7219.patch create mode 100644 0027-Issue-7076-Fix-revert_cache-never-called-in-modrdn-7.patch create mode 100644 0028-Issue-6951-Dynamic-Certificate-refresh-phase-4-Updat.patch create mode 100644 0029-Issue-7224-CI-Test-Simplify-test_reserve_descriptor_.patch create mode 100644 0030-Issue-7178-Bundled-jemalloc-fails-to-build-with-GCC-.patch create mode 100644 0031-Issue-7121-2nd-LeakSanitizer-various-leaks-during-re.patch create mode 100644 0032-Issue-7223-Revert-index-scan-limits-for-system-index.patch create mode 100644 0033-Issue-7223-Add-upgrade-function-to-remove-nsIndexIDL.patch create mode 100644 0034-Issue-7223-Add-upgrade-function-to-remove-ancestorid.patch create mode 100644 0035-Issue-7223-Detect-and-log-index-ordering-mismatch-du.patch create mode 100644 0036-Issue-7223-Add-dsctl-index-check-command-for-offline.patch create mode 100644 0037-Issue-7230-Regression-in-healtcheck-NssCheck-7235.patch create mode 100644 0038-Issue-3555-UI-Fix-audit-issue-with-npm-isaacs-brace-.patch create mode 100644 0039-Issue-7221-CI-tests-fix-some-flaky-tests.patch create mode 100644 0040-Issue-7233-test_produce_division_by_zero-fails-with-.patch create mode 100644 0041-Issue-7241-Drop-dateutil-7242.patch create mode 100644 0042-Issue-7231-Sync-repl-tests-fail-in-FIPS-mode-due-to-.patch create mode 100644 0043-Issue-7248-CLI-attribute-uniqueness-fix-usage-for-ex.patch create mode 100644 0044-Issue-CLI-dsctl-db2index-needs-some-hardening-with-M.patch create mode 100644 0045-Issue-7252-PQC-Need-to-iterate-on-SECOidTag-instead-.patch create mode 100644 0046-Issue-6951-Dynamic-Certificas-Refresh-CI-tests-7238.patch create mode 100644 0047-Issue-7184-2nd-argparse.HelpFormatter-_format_action.patch create mode 100644 0048-Issue-7213-2nd-MDB_BAD_VALSIZE-error-while-handling-.patch create mode 100644 0049-Issue-7223-Use-lexicographical-order-for-ancestorid-.patch create mode 100644 0050-Issue-3134-Fix-build-break-7260.patch create mode 100644 0051-Issue-7066-7052-allow-password-history-to-be-set-to-.patch create mode 100644 0052-Issue-7243-UI-add-support-for-hot-certificates.patch create mode 100644 0053-Issue-6758-Fix-Enable-Replication-dropdown-not-openi.patch create mode 100644 0054-Issue-7223-Remove-integerOrderingMatch-requirement-f.patch create mode 100644 0055-Issue-7236-Fix-GSSAPI-tests-7237.patch create mode 100644 0056-Issue-6753-Port-ticket-49039-test.patch diff --git a/0006-Issue-6753-Port-ticket-548-test-7101.patch b/0006-Issue-6753-Port-ticket-548-test-7101.patch new file mode 100644 index 0000000..e7a6a6c --- /dev/null +++ b/0006-Issue-6753-Port-ticket-548-test-7101.patch @@ -0,0 +1,924 @@ +From fb23c9e366f5eafa6bdbb8cd71afd78e3edefde2 Mon Sep 17 00:00:00 2001 +From: Lenka Doudova +Date: Tue, 13 Jan 2026 10:44:15 +0100 +Subject: [PATCH] Issue 6753 - Port ticket 548 test (#7101) + +Description: +Port ticket 548 test into +dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py + +Relates: #6753 + +Author: Lenka Doudova, aadhikar +Assisted by: Cursor +Reviewer: @droideck(Thanks!) +--- + .../password/pwdPolicy_attribute_test.py | 464 +++++++++++++++++- + dirsrvtests/tests/tickets/ticket548_test.py | 408 --------------- + 2 files changed, 463 insertions(+), 409 deletions(-) + delete mode 100644 dirsrvtests/tests/tickets/ticket548_test.py + +diff --git a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py +index d0c172f94..d021f4720 100644 +--- a/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py ++++ b/dirsrvtests/tests/suites/password/pwdPolicy_attribute_test.py +@@ -9,12 +9,12 @@ + import pytest + from lib389.tasks import * + from lib389.utils import * +-import pdb + from lib389.topologies import topology_st + from lib389.pwpolicy import PwPolicyManager + from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES + from lib389.idm.organizationalunit import OrganizationalUnits + from lib389._constants import (DEFAULT_SUFFIX, DN_DM, PASSWORD) ++from lib389.idm.directorymanager import DirectoryManager + + pytestmark = pytest.mark.tier1 + +@@ -361,6 +361,468 @@ def test_pwdpolicysubentry(topology_st, password_policy): + assert 'nsPwPolicyEntry_user' not in pwp_subentry + + ++@pytest.fixture(scope="function") ++def shadowUser(request, topology_st): ++ """ Create a user with shadowAccount objectclass """ ++ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX) ++ shadowUser = users.create(properties={ ++ 'objectclass': ['top', 'person', 'organizationalPerson', ++ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'], ++ 'sn': '1', ++ 'cn': 'shadowUser', ++ 'uid': 'shadowUser', ++ 'uidNumber': '1', ++ 'gidNumber': '11', ++ 'homeDirectory': '/home/shadowUser', ++ 'displayName': 'Shadow User', ++ 'givenname': 'Shadow', ++ 'mail':f'shadowuser@{DEFAULT_SUFFIX}', ++ 'userpassword': 'password' ++ }) ++ ++ def fin(): ++ if shadowUser.exists(): ++ shadowUser.delete() ++ ++ request.addfinalizer(fin) ++ ++ return shadowUser ++ ++ ++def days_to_secs(days): ++ """ Convert days to seconds """ ++ return days * 86400 ++ ++ ++def check_shadow_attr_value(inst, user_dn, attr_type, expected): ++ """ Check that shadowAccount attribute has expected value """ ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ user = UserAccount(inst, user_dn) ++ assert user.present(attr_type), f'Entry {user_dn} does not have {attr_type} attribute' ++ actual = int(user.get_attr_val_utf8(attr_type)) ++ assert actual == expected, f'{attr_type} of entry {user_dn} is {actual}, expected {expected}' ++ log.info(f'{attr_type} of entry {user_dn} has expected value {actual}') ++ ++ ++def setup_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None): ++ """ Setup password policy """ ++ ++ log.info(f'Setting up {policy} password policy for {dn}') ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ ++ log.info(f'Configuring {policy} password policy') ++ ++ assert policy == 'global' or dn, 'dn is required for non-global policy' ++ ++ if not policy_props: ++ policy_props = { ++ 'passwordMinAge': str(days_to_secs(1)), ++ 'passwordExp': 'on', ++ 'passwordMaxAge': str(days_to_secs(10)), ++ 'passwordWarning': str(days_to_secs(3)) ++ } ++ ++ if policy == 'global': ++ pwp_mgr.set_global_policy(policy_props) ++ elif policy == 'subtree': ++ pwp_mgr.create_subtree_policy(dn, policy_props) ++ elif policy == 'user': ++ pwp_mgr.create_user_policy(dn, policy_props) ++ else: ++ raise ValueError(f'Invalid type of password policy: {policy}') ++ ++ ++def modify_pwp(inst, pwp_mgr, policy, dn=None, policy_props=None): ++ """ Modify password policy """ ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ ++ assert policy == 'global' or dn, 'dn is required for non-global policy' ++ ++ if not policy_props: ++ policy_props = { ++ 'passwordMinAge': str(days_to_secs(3)), ++ 'passwordMaxAge': str(days_to_secs(30)), ++ 'passwordWarning': str(days_to_secs(9)) ++ } ++ ++ if policy == 'global': ++ pwp_mgr.set_global_policy(properties=policy_props) ++ elif policy in ['subtree', 'user']: ++ policy_entry = pwp_mgr.get_pwpolicy_entry(dn) ++ policy_entry.replace_many(*policy_props.items()) ++ else: ++ raise ValueError(f'Invalid type of password policy: {policy}') ++ log.info(f'Modified {policy} policy with {policy_props}.') ++ ++@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented") ++def test_shadowaccount_no_policy(topology_st, shadowUser): ++ """Check shadowAccount under no password policy ++ ++ :id: a1b2c3d4-5e6f-7890-abcd-ef1234567890 ++ :setup: Standalone instance ++ :steps: ++ 1. Add a user with shadowAccount objectclass ++ 2. Bind as the user ++ 3. Check shadowLastChange attribute is set correctly ++ :expectedresults: ++ 1. User is added successfully ++ 2. Bind is successful ++ 3. shadowLastChange is set correctly (days since epoch) ++ """ ++ ++ edate = int(time.time() / (60 * 60 * 24)) ++ ++ log.info(f"Bind as {shadowUser.dn}") ++ shadowUser.rebind('password') ++ check_shadow_attr_value(topology_st.standalone, shadowUser.dn, ++ 'shadowLastChange', edate) ++ ++ ++@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented") ++def test_shadowaccount_global_policy(topology_st, shadowUser, request): ++ """Check shadowAccount with global password policy ++ ++ :id: b2c3d4e5-6f7a-8901-bcde-f23456789012 ++ :setup: Standalone instance ++ :steps: ++ 1. Set global password policy ++ 2. Add a second shadowAccount user ++ 3. Bind as each user and check shadowAccount attributes ++ 4. Modify global password policy ++ 5. Change user password (as the user, not DM) ++ 6. Re-bind with new password ++ 7. Check shadowAccount attributes are updated ++ 8. Clean up - delete second user and reset policy ++ :expectedresults: ++ 1. Global password policy is set successfully ++ 2. Second user is added ++ 3. shadowAccount attributes match policy values for both users ++ 4. Password policy is modified successfully ++ 5. Password is changed successfully ++ 6. Re-bind with new password is successful ++ 7. shadowAccount attributes are updated to match new policy values ++ 8. Cleanup is successful ++ """ ++ inst = topology_st.standalone ++ ++ log.info('Create second shadowAccount user') ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ shadowUser2 = users.create(properties={ ++ 'objectclass': ['top', 'person', 'organizationalPerson', ++ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'], ++ 'sn': '2', ++ 'cn': 'shadowUser2', ++ 'uid': 'shadowUser2', ++ 'uidNumber': '2', ++ 'gidNumber': '22', ++ 'homeDirectory': '/home/shadowUser2', ++ 'displayName': 'Shadow User 2', ++ 'givenname': 'Shadow2', ++ 'mail': f'shadowuser2@{DEFAULT_SUFFIX}', ++ 'userpassword': 'password' ++ }) ++ ++ def fin(): ++ log.info('Clean up - delete second user and reset global policy') ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ try: ++ shadowUser2.delete() ++ except Exception: ++ pass ++ inst.config.replace('passwordMinAge', '0') ++ inst.config.replace('passwordMaxAge', '8640000') ++ inst.config.replace('passwordWarning', '86400') ++ inst.config.replace('passwordExp', 'off') ++ request.addfinalizer(fin) ++ ++ log.info('Configure global password policy') ++ pwp_mgr = PwPolicyManager(inst) ++ setup_pwp(inst, pwp_mgr, 'global') ++ ++ edate = int(time.time() / (60 * 60 * 24)) ++ ++ log.info('Verify attributes of shadowUser (user 1)') ++ shadowUser.rebind('password') ++ check_shadow_attr_value(inst, shadowUser.dn, ++ 'shadowLastChange', edate) ++ check_shadow_attr_value(inst, shadowUser.dn, ++ 'shadowMin', 1) ++ check_shadow_attr_value(inst, shadowUser.dn, ++ 'shadowMax', 10) ++ check_shadow_attr_value(inst, shadowUser.dn, ++ 'shadowWarning', 3) ++ ++ log.info('Verify attributes of shadowUser2 (user 2)') ++ shadowUser2.rebind('password') ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowLastChange', edate) ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowMin', 1) ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowMax', 10) ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowWarning', 3) ++ ++ log.info('Modify global password policy') ++ modify_pwp(inst, pwp_mgr, 'global') ++ ++ log.info('Change shadowUser2 password as the user') ++ shadowUser2.rebind('password') ++ shadowUser2.replace('userpassword', 'password2') ++ time.sleep(1) ++ ++ log.info('Re-bind as shadowUser2 with new password') ++ shadowUser2.rebind('password2') ++ ++ log.info('Verify modified shadowUser2 attributes') ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowMin', 3) ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowMax', 30) ++ check_shadow_attr_value(inst, shadowUser2.dn, ++ 'shadowWarning', 9) ++ ++ ++@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented") ++def test_shadowaccount_subtree_policy(topology_st, request): ++ """Check shadowAccount with subtree level password policy ++ ++ :id: c3d4e5f6-7a8b-9012-cdef-345678901234 ++ :setup: Standalone instance ++ :steps: ++ 1. Create subtree password policy for DEFAULT_SUFFIX with passwordMustChange on ++ 2. Add a new shadowAccount user under the subtree ++ 3. Check shadowLastChange is 0 (since passwordMustChange is on) ++ 4. Verify search as user fails with UNWILLING_TO_PERFORM ++ 5. Change user password (as user) ++ 6. Re-bind with new password ++ 7. Check shadowAccount attributes are updated with correct values ++ 8. Clean up subtree password policy ++ :expectedresults: ++ 1. Subtree password policy is created successfully ++ 2. User is added successfully ++ 3. shadowLastChange is 0 until password is changed ++ 4. Search fails with UNWILLING_TO_PERFORM as expected ++ 5. Password is changed successfully ++ 6. Re-bind with new password is successful ++ 7. shadowAccount attributes are updated to match policy values ++ 8. Subtree password policy is deleted successfully ++ """ ++ inst = topology_st.standalone ++ subtree_dn = DEFAULT_SUFFIX ++ ++ log.info('Configure subtree password policy with passwordMustChange on') ++ properties = { ++ 'passwordMustChange': 'on', ++ 'passwordExp': 'on', ++ 'passwordMinAge': str(days_to_secs(2)), ++ 'passwordMaxAge': str(days_to_secs(20)), ++ 'passwordWarning': str(days_to_secs(6)), ++ 'passwordChange': 'on', ++ 'passwordStorageScheme': 'clear' ++ } ++ ++ pwp_mgr = PwPolicyManager(inst) ++ setup_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=properties) ++ ++ def fin(): ++ log.info('Clean up: delete subtree password policy') ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ try: ++ pwp_mgr.delete_local_policy(subtree_dn) ++ except Exception: ++ pass ++ try: ++ subtree_user.delete() ++ except Exception: ++ pass ++ request.addfinalizer(fin) ++ ++ log.info('Add a new shadowAccount user under the subtree') ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ subtree_user = users.create(properties={ ++ 'objectclass': ['top', 'person', 'organizationalPerson', ++ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'], ++ 'sn': '3', ++ 'cn': 'subtreeUser', ++ 'uid': 'subtreeUser', ++ 'uidNumber': '3', ++ 'gidNumber': '33', ++ 'homeDirectory': '/home/subtreeUser', ++ 'displayName': 'Subtree User', ++ 'givenname': 'Subtree', ++ 'mail': f'subtreeuser@{DEFAULT_SUFFIX}', ++ 'userpassword': 'password' ++ }) ++ ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ ++ log.info('Verify shadowLastChange is 0 since passwordMustChange is on') ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowLastChange', 0) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowMin', 2) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowMax', 20) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowWarning', 6) ++ ++ log.info(f'Bind as {subtree_user.dn} and verify search fails with UNWILLING_TO_PERFORM') ++ subtree_user.rebind('password') ++ with pytest.raises(ldap.UNWILLING_TO_PERFORM): ++ subtree_user.exists() ++ ++ log.info('Modify subtree password policy') ++ dm.rebind() ++ modify_properties = { ++ 'passwordMinAge': str(days_to_secs(4)), ++ 'passwordMaxAge': str(days_to_secs(40)), ++ 'passwordWarning': str(days_to_secs(12)) ++ } ++ modify_pwp(inst, pwp_mgr, 'subtree', dn=subtree_dn, policy_props=modify_properties) ++ ++ log.info(f'Change {subtree_user.dn} password as the user') ++ subtree_user.rebind('password') ++ subtree_user.replace('userpassword', 'password0') ++ time.sleep(1) ++ ++ log.info(f'Re-bind as {subtree_user.dn} with new password') ++ subtree_user.rebind('password0') ++ ++ edate = int(time.time() / (60 * 60 * 24)) ++ ++ log.info('Verify shadowLastChange is now set to today after password change') ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowLastChange', edate) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowMin', 4) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowMax', 40) ++ check_shadow_attr_value(inst, subtree_user.dn, ++ 'shadowWarning', 12) ++ ++ ++@pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented") ++def test_shadowaccount_user_policy(topology_st, request): ++ """Check shadowAccount with user level password policy ++ ++ :id: d4e5f6a7-8b9c-0123-def0-456789012345 ++ :setup: Standalone instance ++ :steps: ++ 1. Create a new shadowAccount user ++ 2. Create user password policy ++ 3. Verify shadowAccount attributes match policy values ++ 4. Modify user password policy ++ 5. Change user password ++ 6. Re-bind with new password ++ 7. Check shadowAccount attributes are updated ++ 8. Clean up user password policy ++ :expectedresults: ++ 1. User is created successfully ++ 2. User password policy is created successfully ++ 3. shadowAccount attributes match policy values ++ 4. Password policy is modified successfully ++ 5. Password is changed successfully ++ 6. Re-bind with new password is successful ++ 7. shadowAccount attributes are updated to match new policy values ++ 8. User password policy is deleted successfully ++ """ ++ inst = topology_st.standalone ++ ++ log.info('Create a new shadowAccount user for user policy test') ++ users = UserAccounts(inst, DEFAULT_SUFFIX) ++ user_policy_user = users.create(properties={ ++ 'objectclass': ['top', 'person', 'organizationalPerson', ++ 'inetOrgPerson', 'extensibleObject', 'shadowAccount'], ++ 'sn': '4', ++ 'cn': 'userPolicyUser', ++ 'uid': 'userPolicyUser', ++ 'uidNumber': '4', ++ 'gidNumber': '44', ++ 'homeDirectory': '/home/userPolicyUser', ++ 'displayName': 'User Policy User', ++ 'givenname': 'UserPolicy', ++ 'mail': f'userpolicyuser@{DEFAULT_SUFFIX}', ++ 'userpassword': 'password' ++ }) ++ ++ pwp_mgr = PwPolicyManager(inst) ++ ++ def fin(): ++ log.info('Clean up: delete user password policy and user') ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ try: ++ pwp_mgr.delete_local_policy(user_policy_user.dn) ++ except Exception: ++ pass ++ try: ++ user_policy_user.delete() ++ except Exception: ++ pass ++ request.addfinalizer(fin) ++ ++ log.info('Configure user password policy') ++ properties = { ++ 'passwordExp': 'on', ++ 'passwordMinAge': str(days_to_secs(2)), ++ 'passwordMaxAge': str(days_to_secs(20)), ++ 'passwordWarning': str(days_to_secs(6)), ++ 'passwordChange': 'on', ++ 'passwordStorageScheme': 'clear' ++ } ++ setup_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=properties) ++ ++ edate = int(time.time() / (60 * 60 * 24)) ++ ++ dm = DirectoryManager(inst) ++ dm.rebind() ++ ++ log.info('Verify shadowAccount attributes match user policy') ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowLastChange', edate) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowMin', 2) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowMax', 20) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowWarning', 6) ++ ++ log.info('Modify user password policy') ++ modify_properties = { ++ 'passwordMinAge': str(days_to_secs(4)), ++ 'passwordMaxAge': str(days_to_secs(40)), ++ 'passwordWarning': str(days_to_secs(12)) ++ } ++ modify_pwp(inst, pwp_mgr, 'user', dn=user_policy_user.dn, policy_props=modify_properties) ++ ++ log.info(f'Change {user_policy_user.dn} password as the user') ++ user_policy_user.rebind('password') ++ user_policy_user.replace('userpassword', 'password0') ++ time.sleep(1) ++ ++ log.info(f'Re-bind as {user_policy_user.dn} with new password') ++ user_policy_user.rebind('password0') ++ ++ edate = int(time.time() / (60 * 60 * 24)) ++ ++ log.info('Verify shadowAccount attributes are updated after password change') ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowLastChange', edate) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowMin', 4) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowMax', 40) ++ check_shadow_attr_value(inst, user_policy_user.dn, ++ 'shadowWarning', 12) ++ ++ + if __name__ == '__main__': + # Run isolated + # -s for DEBUG mode +diff --git a/dirsrvtests/tests/tickets/ticket548_test.py b/dirsrvtests/tests/tickets/ticket548_test.py +deleted file mode 100644 +index cac3cc5f8..000000000 +--- a/dirsrvtests/tests/tickets/ticket548_test.py ++++ /dev/null +@@ -1,408 +0,0 @@ +-# --- BEGIN COPYRIGHT BLOCK --- +-# Copyright (C) 2016 Red Hat, Inc. +-# All rights reserved. +-# +-# License: GPL (version 3 or any later version). +-# See LICENSE for details. +-# --- END COPYRIGHT BLOCK --- +-# +-import pytest +-from lib389.tasks import * +-from lib389.utils import * +-from lib389.topologies import topology_st +- +-from lib389._constants import DEFAULT_SUFFIX, DN_CONFIG, DN_DM, PASSWORD, DEFAULT_SUFFIX_ESCAPED +- +-# Skip on older versions +-pytestmark = [pytest.mark.tier2, +- pytest.mark.skipif(ds_is_older('1.3.6'), reason="Not implemented")] +- +-log = logging.getLogger(__name__) +- +-# Assuming DEFAULT_SUFFIX is "dc=example,dc=com", otherwise it does not work... :( +-SUBTREE_CONTAINER = 'cn=nsPwPolicyContainer,' + DEFAULT_SUFFIX +-SUBTREE_PWPDN = 'cn=nsPwPolicyEntry,' + DEFAULT_SUFFIX +-SUBTREE_PWP = 'cn=cn\3DnsPwPolicyEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER +-SUBTREE_COS_TMPLDN = 'cn=nsPwTemplateEntry,' + DEFAULT_SUFFIX +-SUBTREE_COS_TMPL = 'cn=cn\3DnsPwTemplateEntry\2C' + DEFAULT_SUFFIX_ESCAPED + ',' + SUBTREE_CONTAINER +-SUBTREE_COS_DEF = 'cn=nsPwPolicy_CoS,' + DEFAULT_SUFFIX +- +-USER1_DN = 'uid=user1,' + DEFAULT_SUFFIX +-USER2_DN = 'uid=user2,' + DEFAULT_SUFFIX +-USER3_DN = 'uid=user3,' + DEFAULT_SUFFIX +-USER_PW = 'password' +- +- +-def days_to_secs(days): +- # Value of 60 * 60 * 24 +- return days * 86400 +- +- +-# Values are in days +-def set_global_pwpolicy(topology_st, min_=1, max_=10, warn=3): +- log.info(" +++++ Enable global password policy +++++\n") +- # Enable password policy +- try: +- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'nsslapd-pwpolicy-local', b'on')]) +- except ldap.LDAPError as e: +- log.error('Failed to set pwpolicy-local: error ' + e.message['desc']) +- assert False +- +- # Convert our values to seconds +- min_secs = days_to_secs(min_) +- max_secs = days_to_secs(max_) +- warn_secs = days_to_secs(warn) +- +- log.info(" Set global password Min Age -- %s day\n" % min_) +- try: +- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMinAge', ('%s' % min_secs).encode())]) +- except ldap.LDAPError as e: +- log.error('Failed to set passwordMinAge: error ' + e.message['desc']) +- assert False +- +- log.info(" Set global password Expiration -- on\n") +- try: +- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordExp', b'on')]) +- except ldap.LDAPError as e: +- log.error('Failed to set passwordExp: error ' + e.message['desc']) +- assert False +- +- log.info(" Set global password Max Age -- %s days\n" % max_) +- try: +- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordMaxAge', ('%s' % max_secs).encode())]) +- except ldap.LDAPError as e: +- log.error('Failed to set passwordMaxAge: error ' + e.message['desc']) +- assert False +- +- log.info(" Set global password Warning -- %s days\n" % warn) +- try: +- topology_st.standalone.modify_s(DN_CONFIG, [(ldap.MOD_REPLACE, 'passwordWarning', ('%s' % warn_secs).encode())]) +- except ldap.LDAPError as e: +- log.error('Failed to set passwordWarning: error ' + e.message['desc']) +- assert False +- +- +-def set_subtree_pwpolicy(topology_st, min_=2, max_=20, warn=6): +- log.info(" +++++ Enable subtree level password policy +++++\n") +- +- # Convert our values to seconds +- min_secs = days_to_secs(min_) +- max_secs = days_to_secs(max_) +- warn_secs = days_to_secs(warn) +- +- log.info(" Add the container") +- try: +- topology_st.standalone.add_s(Entry((SUBTREE_CONTAINER, {'objectclass': 'top nsContainer'.split(), +- 'cn': 'nsPwPolicyContainer'}))) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error('Failed to add subtree container: error ' + e.message['desc']) +- # assert False +- +- try: +- # Purge the old policy +- topology_st.standalone.delete_s(SUBTREE_PWP) +- except: +- pass +- +- log.info( +- " Add the password policy subentry {passwordMustChange: on, passwordMinAge: %s, passwordMaxAge: %s, passwordWarning: %s}" % ( +- min_, max_, warn)) +- try: +- topology_st.standalone.add_s(Entry((SUBTREE_PWP, {'objectclass': 'top ldapsubentry passwordpolicy'.split(), +- 'cn': SUBTREE_PWPDN, +- 'passwordMustChange': 'on', +- 'passwordExp': 'on', +- 'passwordMinAge': '%s' % min_secs, +- 'passwordMaxAge': '%s' % max_secs, +- 'passwordWarning': '%s' % warn_secs, +- 'passwordChange': 'on', +- 'passwordStorageScheme': 'clear'}))) +- except ldap.LDAPError as e: +- log.error('Failed to add passwordpolicy: error ' + e.message['desc']) +- assert False +- +- log.info(" Add the COS template") +- try: +- topology_st.standalone.add_s( +- Entry((SUBTREE_COS_TMPL, {'objectclass': 'top ldapsubentry costemplate extensibleObject'.split(), +- 'cn': SUBTREE_PWPDN, +- 'cosPriority': '1', +- 'cn': SUBTREE_COS_TMPLDN, +- 'pwdpolicysubentry': SUBTREE_PWP}))) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error('Failed to add COS template: error ' + e.message['desc']) +- # assert False +- +- log.info(" Add the COS definition") +- try: +- topology_st.standalone.add_s( +- Entry((SUBTREE_COS_DEF, {'objectclass': 'top ldapsubentry cosSuperDefinition cosPointerDefinition'.split(), +- 'cn': SUBTREE_PWPDN, +- 'costemplatedn': SUBTREE_COS_TMPL, +- 'cosAttribute': 'pwdpolicysubentry default operational-default'}))) +- except ldap.ALREADY_EXISTS: +- pass +- except ldap.LDAPError as e: +- log.error('Failed to add COS def: error ' + e.message['desc']) +- # assert False +- +- time.sleep(1) +- +- +-def update_passwd(topology_st, user, passwd, newpasswd): +- log.info(" Bind as {%s,%s}" % (user, passwd)) +- topology_st.standalone.simple_bind_s(user, passwd) +- try: +- topology_st.standalone.modify_s(user, [(ldap.MOD_REPLACE, 'userpassword', newpasswd.encode())]) +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to update the password ' + cpw + ' of user ' + user + ': error ' + e.message[ +- 'desc']) +- assert False +- +- time.sleep(1) +- +- +-def check_shadow_attr_value(entry, attr_type, expected, dn): +- if entry.hasAttr(attr_type): +- actual = entry.getValue(attr_type) +- if int(actual) == expected: +- log.info('%s of entry %s has expected value %s' % (attr_type, dn, actual)) +- assert True +- else: +- log.fatal('%s %s of entry %s does not have expected value %s' % (attr_type, actual, dn, expected)) +- assert False +- else: +- log.fatal('entry %s does not have %s attr' % (dn, attr_type)) +- assert False +- +- +-def test_ticket548_test_with_no_policy(topology_st): +- """ +- Check shadowAccount under no password policy +- """ +- log.info("Case 1. No password policy") +- +- log.info("Bind as %s" % DN_DM) +- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) +- +- log.info('Add an entry' + USER1_DN) +- try: +- topology_st.standalone.add_s( +- Entry((USER1_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(), +- 'sn': '1', +- 'cn': 'user 1', +- 'uid': 'user1', +- 'givenname': 'user', +- 'mail': 'user1@' + DEFAULT_SUFFIX, +- 'userpassword': USER_PW}))) +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to add user' + USER1_DN + ': error ' + e.message['desc']) +- assert False +- +- edate = int(time.time() / (60 * 60 * 24)) +- log.info('Search entry %s' % USER1_DN) +- +- log.info("Bind as %s" % USER1_DN) +- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW) +- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)", ['shadowLastChange']) +- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN) +- +- log.info("Check shadowAccount with no policy was successfully verified.") +- +- +-def test_ticket548_test_global_policy(topology_st): +- """ +- Check shadowAccount with global password policy +- """ +- +- log.info("Case 2. Check shadowAccount with global password policy") +- +- log.info("Bind as %s" % DN_DM) +- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) +- +- set_global_pwpolicy(topology_st) +- +- log.info('Add an entry' + USER2_DN) +- try: +- topology_st.standalone.add_s( +- Entry((USER2_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(), +- 'sn': '2', +- 'cn': 'user 2', +- 'uid': 'user2', +- 'givenname': 'user', +- 'mail': 'user2@' + DEFAULT_SUFFIX, +- 'userpassword': USER_PW}))) +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to add user' + USER2_DN + ': error ' + e.message['desc']) +- assert False +- +- edate = int(time.time() / (60 * 60 * 24)) +- +- log.info("Bind as %s" % USER1_DN) +- topology_st.standalone.simple_bind_s(USER1_DN, USER_PW) +- +- log.info('Search entry %s' % USER1_DN) +- entry = topology_st.standalone.getEntry(USER1_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER1_DN) +- +- # passwordMinAge -- 1 day +- check_shadow_attr_value(entry, 'shadowMin', 1, USER1_DN) +- +- # passwordMaxAge -- 10 days +- check_shadow_attr_value(entry, 'shadowMax', 10, USER1_DN) +- +- # passwordWarning -- 3 days +- check_shadow_attr_value(entry, 'shadowWarning', 3, USER1_DN) +- +- log.info("Bind as %s" % USER2_DN) +- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW) +- +- log.info('Search entry %s' % USER2_DN) +- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER2_DN) +- +- # passwordMinAge -- 1 day +- check_shadow_attr_value(entry, 'shadowMin', 1, USER2_DN) +- +- # passwordMaxAge -- 10 days +- check_shadow_attr_value(entry, 'shadowMax', 10, USER2_DN) +- +- # passwordWarning -- 3 days +- check_shadow_attr_value(entry, 'shadowWarning', 3, USER2_DN) +- +- # Bind as DM again, change policy +- log.info("Bind as %s" % DN_DM) +- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) +- set_global_pwpolicy(topology_st, 3, 30, 9) +- +- # change the user password, then check again. +- log.info("Bind as %s" % USER2_DN) +- topology_st.standalone.simple_bind_s(USER2_DN, USER_PW) +- +- newpasswd = USER_PW + '2' +- update_passwd(topology_st, USER2_DN, USER_PW, newpasswd) +- +- log.info("Re-bind as %s with new password" % USER2_DN) +- topology_st.standalone.simple_bind_s(USER2_DN, newpasswd) +- +- ## This tests if we update the shadow values on password change. +- log.info('Search entry %s' % USER2_DN) +- entry = topology_st.standalone.getEntry(USER2_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- +- # passwordMinAge -- 1 day +- check_shadow_attr_value(entry, 'shadowMin', 3, USER2_DN) +- +- # passwordMaxAge -- 10 days +- check_shadow_attr_value(entry, 'shadowMax', 30, USER2_DN) +- +- # passwordWarning -- 3 days +- check_shadow_attr_value(entry, 'shadowWarning', 9, USER2_DN) +- +- log.info("Check shadowAccount with global policy was successfully verified.") +- +- +-def test_ticket548_test_subtree_policy(topology_st): +- """ +- Check shadowAccount with subtree level password policy +- """ +- +- log.info("Case 3. Check shadowAccount with subtree level password policy") +- +- log.info("Bind as %s" % DN_DM) +- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) +- # Check the global policy values +- +- set_subtree_pwpolicy(topology_st, 2, 20, 6) +- +- log.info('Add an entry' + USER3_DN) +- try: +- topology_st.standalone.add_s( +- Entry((USER3_DN, {'objectclass': "top person organizationalPerson inetOrgPerson shadowAccount".split(), +- 'sn': '3', +- 'cn': 'user 3', +- 'uid': 'user3', +- 'givenname': 'user', +- 'mail': 'user3@' + DEFAULT_SUFFIX, +- 'userpassword': USER_PW}))) +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to add user' + USER3_DN + ': error ' + e.message['desc']) +- assert False +- +- log.info('Search entry %s' % USER3_DN) +- entry0 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- +- log.info('Expecting shadowLastChange 0 since passwordMustChange is on') +- check_shadow_attr_value(entry0, 'shadowLastChange', 0, USER3_DN) +- +- # passwordMinAge -- 2 day +- check_shadow_attr_value(entry0, 'shadowMin', 2, USER3_DN) +- +- # passwordMaxAge -- 20 days +- check_shadow_attr_value(entry0, 'shadowMax', 20, USER3_DN) +- +- # passwordWarning -- 6 days +- check_shadow_attr_value(entry0, 'shadowWarning', 6, USER3_DN) +- +- log.info("Bind as %s" % USER3_DN) +- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW) +- +- log.info('Search entry %s' % USER3_DN) +- try: +- entry1 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- except ldap.UNWILLING_TO_PERFORM: +- log.info('test_ticket548: Search by' + USER3_DN + ' failed by UNWILLING_TO_PERFORM as expected') +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc']) +- assert False +- +- log.info("Bind as %s and updating the password with a new one" % USER3_DN) +- topology_st.standalone.simple_bind_s(USER3_DN, USER_PW) +- +- # Bind as DM again, change policy +- log.info("Bind as %s" % DN_DM) +- topology_st.standalone.simple_bind_s(DN_DM, PASSWORD) +- +- set_subtree_pwpolicy(topology_st, 4, 40, 12) +- +- newpasswd = USER_PW + '0' +- update_passwd(topology_st, USER3_DN, USER_PW, newpasswd) +- +- log.info("Re-bind as %s with new password" % USER3_DN) +- topology_st.standalone.simple_bind_s(USER3_DN, newpasswd) +- +- try: +- entry2 = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- except ldap.LDAPError as e: +- log.fatal('test_ticket548: Failed to serch user' + USER3_DN + ' by self: error ' + e.message['desc']) +- assert False +- +- edate = int(time.time() / (60 * 60 * 24)) +- +- log.info('Expecting shadowLastChange %d once userPassword is updated', edate) +- check_shadow_attr_value(entry2, 'shadowLastChange', edate, USER3_DN) +- +- log.info('Search entry %s' % USER3_DN) +- entry = topology_st.standalone.getEntry(USER3_DN, ldap.SCOPE_BASE, "(objectclass=*)") +- check_shadow_attr_value(entry, 'shadowLastChange', edate, USER3_DN) +- +- # passwordMinAge -- 1 day +- check_shadow_attr_value(entry, 'shadowMin', 4, USER3_DN) +- +- # passwordMaxAge -- 10 days +- check_shadow_attr_value(entry, 'shadowMax', 40, USER3_DN) +- +- # passwordWarning -- 3 days +- check_shadow_attr_value(entry, 'shadowWarning', 12, USER3_DN) +- +- log.info("Check shadowAccount with subtree level policy was successfully verified.") +- +- +-if __name__ == '__main__': +- # Run isolated +- # -s for DEBUG mode +- CURRENT_FILE = os.path.realpath(__file__) +- pytest.main("-s %s" % CURRENT_FILE) +-- +2.52.0 + diff --git a/0007-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch b/0007-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch new file mode 100644 index 0000000..0baedef --- /dev/null +++ b/0007-Issue-7152-ns-slapd-fails-to-shutdown-when-deferred-.patch @@ -0,0 +1,82 @@ +From 7c8a16c6bed524fb54d18a5b7e93d4bd5bb19d49 Mon Sep 17 00:00:00 2001 +From: Viktor Ashirov +Date: Wed, 14 Jan 2026 17:55:29 +0100 +Subject: [PATCH] Issue 7152 - ns-slapd fails to shutdown when deferred + memberof update is in progress (#7187) + +Bug Description: +When a deferred memberof update is in progress during shutdown, the +backend operations (add, modify, delete, modrdn) wait in a polling loop +for the deferred task to complete. However, if the deferred thread exits +before clearing the SLAPI_DEFERRED_MEMBEROF flag, the loop becomes +infinite, causing the server to hang during shutdown. + +Fix Description: +Add additional check to the polling loops so they exit immediately when +the server is shutting down. + +Fixes: https://github.com/389ds/389-ds-base/issues/7152 + +Reviewed by: @tbordaz (Thanks!) +--- + ldap/servers/slapd/back-ldbm/ldbm_add.c | 2 +- + ldap/servers/slapd/back-ldbm/ldbm_delete.c | 2 +- + ldap/servers/slapd/back-ldbm/ldbm_modify.c | 2 +- + ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 2 +- + 4 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_add.c b/ldap/servers/slapd/back-ldbm/ldbm_add.c +index db6024636..90d5abc3d 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_add.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_add.c +@@ -1452,7 +1452,7 @@ common_return: + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + if (deferred) { + PRIntervalTime delay = PR_MillisecondsToInterval(100); +- while (deferred) { ++ while (deferred && !g_get_shutdown()) { + DS_Sleep(delay); + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + } +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_delete.c b/ldap/servers/slapd/back-ldbm/ldbm_delete.c +index 498342f2d..cbfb5bca9 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_delete.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_delete.c +@@ -1536,7 +1536,7 @@ diskfull_return: + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + if (deferred) { + PRIntervalTime delay = PR_MillisecondsToInterval(100); +- while (deferred) { ++ while (deferred && !g_get_shutdown()) { + DS_Sleep(delay); + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + } +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c +index ea49a4c56..c57ba43ae 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c +@@ -1179,7 +1179,7 @@ common_return: + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + if (deferred) { + PRIntervalTime delay = PR_MillisecondsToInterval(100); +- while (deferred) { ++ while (deferred && !g_get_shutdown()) { + DS_Sleep(delay); + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + } +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +index 018ad9e49..759edb80d 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c +@@ -1469,7 +1469,7 @@ common_return: + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + if (deferred) { + PRIntervalTime delay = PR_MillisecondsToInterval(100); +- while (deferred) { ++ while (deferred && !g_get_shutdown()) { + DS_Sleep(delay); + slapi_pblock_get(pb, SLAPI_DEFERRED_MEMBEROF, &deferred); + } +-- +2.52.0 + diff --git a/0008-Issue-7169-Fix-automember_plugin-CI-test-failures-71.patch b/0008-Issue-7169-Fix-automember_plugin-CI-test-failures-71.patch new file mode 100644 index 0000000..e5c6916 --- /dev/null +++ b/0008-Issue-7169-Fix-automember_plugin-CI-test-failures-71.patch @@ -0,0 +1,61 @@ +From a84a6a7d8323316bfa4055729a3604a0fcfebaf9 Mon Sep 17 00:00:00 2001 +From: Akshay Adhikari +Date: Fri, 16 Jan 2026 19:35:42 +0530 +Subject: [PATCH] Issue 7169 - Fix automember_plugin CI test failures (#7181) + +Description: Issue 7053 removed member cleanup from MemberOf plugin, +transferring it to Referential Integrity plugin. Enable this plugin +in automember tests and clean up groups before rebuild task tests. + +Fixes: #7169 + +Reviewed by: @progier389 +--- + .../tests/suites/automember_plugin/basic_test.py | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/dirsrvtests/tests/suites/automember_plugin/basic_test.py b/dirsrvtests/tests/suites/automember_plugin/basic_test.py +index 6f2cf3326..f3629c811 100644 +--- a/dirsrvtests/tests/suites/automember_plugin/basic_test.py ++++ b/dirsrvtests/tests/suites/automember_plugin/basic_test.py +@@ -19,7 +19,8 @@ from lib389.idm.organizationalunit import OrganizationalUnits + from lib389.idm.domain import Domain + from lib389.idm.posixgroup import PosixGroups + from lib389.plugins import AutoMembershipPlugin, AutoMembershipDefinitions, \ +- MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin ++ MemberOfPlugin, AutoMembershipRegexRules, AutoMembershipDefinition, RetroChangelogPlugin, \ ++ ReferentialIntegrityPlugin + from lib389.backend import Backends + from lib389.config import Config + from lib389._constants import DEFAULT_SUFFIX +@@ -196,6 +197,7 @@ def _create_all_entries(topo): + auto = AutoMembershipPlugin(topo.ms["supplier1"]) + auto.add("nsslapd-pluginConfigArea", "cn=autoMembersPlugin,{}".format(BASE_REPL)) + MemberOfPlugin(topo.ms["supplier1"]).enable() ++ ReferentialIntegrityPlugin(topo.ms["supplier1"]).enable() + automembers_definitions = AutoMembershipDefinitions(topo.ms["supplier1"]) + automembers_definitions.create(properties={ + 'cn': 'userGroups', +@@ -950,8 +952,18 @@ def _startuptask(topo): + + @pytest.fixture(scope="function") + def _fixture_for_build_task(request, topo): ++ supplier = topo.ms['supplier1'] ++ managers_grp = "cn=Managers,ou=userGroups,{}".format(BASE_SUFF) ++ contract_grp = "cn=Contractors,ou=userGroups,{}".format(BASE_SUFF) ++ ++ for grp in (managers_grp, contract_grp): ++ group = Group(supplier, grp) ++ try: ++ group.remove_all('member') ++ except ldap.NO_SUCH_ATTRIBUTE: ++ pass ++ + def finof(): +- supplier = topo.ms['supplier1'] + auto_mem_scope = "ou=TaskEmployees,{}".format(BASE_SUFF) + for user in nsAdminGroups(supplier, auto_mem_scope, rdn=None).list(): + user.delete() +-- +2.52.0 + diff --git a/0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch b/0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch new file mode 100644 index 0000000..9fd2e71 --- /dev/null +++ b/0009-Issue-6758-Use-OUIA-selectors-for-WebUI-plugin-tests.patch @@ -0,0 +1,115 @@ +From 5b32479abcd68f8b37d2fb207c502113a5b4b16c Mon Sep 17 00:00:00 2001 +From: Akshay Adhikari +Date: Mon, 19 Jan 2026 19:45:29 +0530 +Subject: [PATCH] Issue 6758 - Use OUIA selectors for WebUI plugin tests + (#7182) + +Description: +Add ouiaId to plugin NavItems and update tests to use OUIA selectors +instead of text matching. + +Relates: #6758 + +Reviewed by: @droideck +--- + .../suites/webui/plugins/plugins_test.py | 28 +++++++++---------- + src/cockpit/389-console/src/plugins.jsx | 2 +- + 2 files changed, 15 insertions(+), 15 deletions(-) + +diff --git a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py +index e4bd7f039..a849bfb91 100644 +--- a/dirsrvtests/tests/suites/webui/plugins/plugins_test.py ++++ b/dirsrvtests/tests/suites/webui/plugins/plugins_test.py +@@ -168,8 +168,8 @@ def test_linked_attributes_plugin_visibility(topology_st, page, browser_name): + + log.info('Click on Plugins tab, click on Linked Attributes plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('Linked Attributes').wait_for() +- frame.get_by_text('Linked Attributes').click() ++ frame.locator('[data-ouia-component-id="linkedAttributes"]').wait_for() ++ frame.locator('[data-ouia-component-id="linkedAttributes"]').click() + frame.get_by_role('button', name='Add Config').wait_for() + assert frame.get_by_role('button', name='Add Config').is_visible() + +@@ -254,8 +254,8 @@ def test_ldap_pass_through_auth_plugin_visibility(topology_st, page, browser_nam + + log.info('Click on Plugins tab, click on LDAP Pass Through Auth plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('LDAP Pass Through Auth').wait_for() +- frame.get_by_text('LDAP Pass Through Auth').click() ++ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').wait_for() ++ frame.locator('[data-ouia-component-id="passthroughAuthentication"]').click() + frame.get_by_role('button', name='Add URL').wait_for() + assert frame.get_by_role('button', name='Add URL').is_visible() + +@@ -280,8 +280,8 @@ def test_pam_pass_through_auth_plugin_visibility(topology_st, page, browser_name + + log.info('Click on Plugins tab, click on PAM Pass Through Auth plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('PAM Pass Through Auth').wait_for() +- frame.get_by_text('PAM Pass Through Auth').click() ++ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').wait_for() ++ frame.locator('[data-ouia-component-id="pamPassthroughAuthentication"]').click() + frame.get_by_role('button', name='Add Config').wait_for() + assert frame.get_by_role('button', name='Add Config').is_visible() + +@@ -306,8 +306,8 @@ def test_posix_winsync_plugin_visibility(topology_st, page, browser_name): + + log.info('Click on Plugins tab, click on Posix Winsync plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('Posix Winsync').wait_for() +- frame.get_by_text('Posix Winsync').click() ++ frame.locator('[data-ouia-component-id="winsync"]').wait_for() ++ frame.locator('[data-ouia-component-id="winsync"]').click() + frame.locator('#posixWinsyncCreateMemberOfTask').wait_for() + assert frame.locator('#posixWinsyncCreateMemberOfTask').is_visible() + +@@ -332,8 +332,8 @@ def test_referential_integrity_plugin_visibility(topology_st, page, browser_name + + log.info('Click on Plugins tab, click on Referential Integrity plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('Referential Integrity').wait_for() +- frame.get_by_text('Referential Integrity').click() ++ frame.locator('[data-ouia-component-id="referentialIntegrity"]').wait_for() ++ frame.locator('[data-ouia-component-id="referentialIntegrity"]').click() + frame.locator('#entryScope').wait_for() + assert frame.locator('#entryScope').is_visible() + +@@ -358,8 +358,8 @@ def test_retro_changelog_plugin_visibility(topology_st, page, browser_name): + + log.info('Click on Plugins tab, click on Retro Changelog plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('Retro Changelog').wait_for() +- frame.get_by_text('Retro Changelog').click() ++ frame.locator('[data-ouia-component-id="retroChangelog"]').wait_for() ++ frame.locator('[data-ouia-component-id="retroChangelog"]').click() + frame.locator('#isReplicated').wait_for() + assert frame.locator('#isReplicated').is_visible() + +@@ -384,8 +384,8 @@ def test_rootdn_access_control_plugin_visibility(topology_st, page, browser_name + + log.info('Click on Plugins tab, click on RootDN Access Control plugin and check if element is loaded.') + frame.get_by_role('tab', name='Plugins', exact=True).click() +- frame.get_by_text('RootDN Access Control').wait_for() +- frame.get_by_text('RootDN Access Control').click() ++ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').wait_for() ++ frame.locator('[data-ouia-component-id="rootDnaAccessControl"]').click() + frame.locator('#allowMon').wait_for() + assert frame.locator('#allowMon').is_visible() + +diff --git a/src/cockpit/389-console/src/plugins.jsx b/src/cockpit/389-console/src/plugins.jsx +index 4124a77ef..e24255c76 100644 +--- a/src/cockpit/389-console/src/plugins.jsx ++++ b/src/cockpit/389-console/src/plugins.jsx +@@ -680,7 +680,7 @@ export class Plugins extends React.Component { +