400 lines
17 KiB
Diff
400 lines
17 KiB
Diff
From 3ba73d2aa55f18ff73d4b3901ce101133745effc Mon Sep 17 00:00:00 2001
|
|
From: Mark Reynolds <mreynolds@redhat.com>
|
|
Date: Wed, 9 Jul 2025 14:18:50 -0400
|
|
Subject: [PATCH] Issue 6859 - str2filter is not fully applying matching rules
|
|
|
|
Description:
|
|
|
|
When we have an extended filter, one with a MR applied, it is ignored during
|
|
internal searches:
|
|
|
|
"(cn:CaseExactMatch:=Value)"
|
|
|
|
For internal searches we use str2filter() and it doesn't fully apply extended
|
|
search filter matching rules
|
|
|
|
Also needed to update attr uniqueness plugin to apply this change for mod
|
|
operations (previously only Adds were correctly handling these attribute
|
|
filters)
|
|
|
|
Relates: https://github.com/389ds/389-ds-base/issues/6857
|
|
Relates: https://github.com/389ds/389-ds-base/issues/6859
|
|
|
|
Reviewed by: spichugi & tbordaz(Thanks!!)
|
|
---
|
|
.../tests/suites/plugins/attruniq_test.py | 295 +++++++++++++++++-
|
|
ldap/servers/plugins/uiduniq/uid.c | 7 +
|
|
ldap/servers/slapd/plugin_mr.c | 2 +-
|
|
ldap/servers/slapd/str2filter.c | 8 +
|
|
4 files changed, 309 insertions(+), 3 deletions(-)
|
|
|
|
diff --git a/dirsrvtests/tests/suites/plugins/attruniq_test.py b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
|
index b190e0ec1..b338f405f 100644
|
|
--- a/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
|
+++ b/dirsrvtests/tests/suites/plugins/attruniq_test.py
|
|
@@ -1,5 +1,5 @@
|
|
# --- BEGIN COPYRIGHT BLOCK ---
|
|
-# Copyright (C) 2021 Red Hat, Inc.
|
|
+# Copyright (C) 2025 Red Hat, Inc.
|
|
# All rights reserved.
|
|
#
|
|
# License: GPL (version 3 or any later version).
|
|
@@ -80,4 +80,295 @@ def test_modrdn_attr_uniqueness(topology_st):
|
|
log.debug(excinfo.value)
|
|
|
|
log.debug('Move user2 to group1')
|
|
- user2.rename(f'uid={user2.rdn}', group1.dn)
|
|
\ No newline at end of file
|
|
+
|
|
+ user2.rename(f'uid={user2.rdn}', group1.dn)
|
|
+
|
|
+ # Cleanup for next test
|
|
+ user1.delete()
|
|
+ user2.delete()
|
|
+ attruniq.disable()
|
|
+ attruniq.delete()
|
|
+
|
|
+
|
|
+def test_multiple_attr_uniqueness(topology_st):
|
|
+ """ Test that attribute uniqueness works properly with multiple attributes
|
|
+
|
|
+ :id: c49aa5c1-7e65-45fd-b064-55e0b815e9bc
|
|
+ :setup: Standalone instance
|
|
+ :steps:
|
|
+ 1. Setup attribute uniqueness plugin to ensure uniqueness of attributes 'mail' and 'mailAlternateAddress'
|
|
+ 2. Add user with unique 'mail=non-uniq@value.net' and 'mailAlternateAddress=alt-mail@value.net'
|
|
+ 3. Try adding another user with 'mail=non-uniq@value.net'
|
|
+ 4. Try adding another user with 'mailAlternateAddress=alt-mail@value.net'
|
|
+ 5. Try adding another user with 'mail=alt-mail@value.net'
|
|
+ 6. Try adding another user with 'mailAlternateAddress=non-uniq@value.net'
|
|
+ :expectedresults:
|
|
+ 1. Success
|
|
+ 2. Success
|
|
+ 3. Should raise CONSTRAINT_VIOLATION
|
|
+ 4. Should raise CONSTRAINT_VIOLATION
|
|
+ 5. Should raise CONSTRAINT_VIOLATION
|
|
+ 6. Should raise CONSTRAINT_VIOLATION
|
|
+ """
|
|
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
|
+
|
|
+ try:
|
|
+ log.debug(f'Setup PLUGIN_ATTR_UNIQUENESS plugin for {MAIL_ATTR_VALUE} attribute for the group2')
|
|
+ attruniq.create(properties={'cn': 'attruniq'})
|
|
+ attruniq.add_unique_attribute('mail')
|
|
+ attruniq.add_unique_attribute('mailAlternateAddress')
|
|
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
|
+ attruniq.enable_all_subtrees()
|
|
+ log.debug(f'Enable PLUGIN_ATTR_UNIQUENESS plugin as "ON"')
|
|
+ attruniq.enable()
|
|
+ except ldap.LDAPError as e:
|
|
+ log.fatal('test_multiple_attribute_uniqueness: Failed to configure plugin for "mail": error {}'.format(e.args[0]['desc']))
|
|
+ assert False
|
|
+
|
|
+ topology_st.standalone.restart()
|
|
+
|
|
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX)
|
|
+
|
|
+ testuser1 = users.create_test_user(100,100)
|
|
+ testuser1.add('objectclass', 'extensibleObject')
|
|
+ testuser1.add('mail', MAIL_ATTR_VALUE)
|
|
+ testuser1.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
|
+
|
|
+ testuser2 = users.create_test_user(200, 200)
|
|
+ testuser2.add('objectclass', 'extensibleObject')
|
|
+
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ testuser2.add('mail', MAIL_ATTR_VALUE)
|
|
+
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE_ALT)
|
|
+
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ testuser2.add('mail', MAIL_ATTR_VALUE_ALT)
|
|
+
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ testuser2.add('mailAlternateAddress', MAIL_ATTR_VALUE)
|
|
+
|
|
+ # Cleanup
|
|
+ testuser1.delete()
|
|
+ testuser2.delete()
|
|
+ attruniq.disable()
|
|
+ attruniq.delete()
|
|
+
|
|
+
|
|
+def test_exclude_subtrees(topology_st):
|
|
+ """ Test attribute uniqueness with exclude scope
|
|
+
|
|
+ :id: 43d29a60-40e1-4ebd-b897-6ef9f20e9f27
|
|
+ :setup: Standalone instance
|
|
+ :steps:
|
|
+ 1. Setup and enable attribute uniqueness plugin for telephonenumber unique attribute
|
|
+ 2. Create subtrees and test users
|
|
+ 3. Add a unique attribute to a user within uniqueness scope
|
|
+ 4. Add exclude subtree
|
|
+ 5. Try to add existing value attribute to an entry within uniqueness scope
|
|
+ 6. Try to add existing value attribute to an entry within exclude scope
|
|
+ 7. Remove the attribute from affected entries
|
|
+ 8. Add a unique attribute to a user within exclude scope
|
|
+ 9. Try to add existing value attribute to an entry within uniqueness scope
|
|
+ 10. Try to add existing value attribute to another entry within uniqueness scope
|
|
+ 11. Remove the attribute from affected entries
|
|
+ 12. Add another exclude subtree
|
|
+ 13. Add a unique attribute to a user within uniqueness scope
|
|
+ 14. Try to add existing value attribute to an entry within uniqueness scope
|
|
+ 15. Try to add existing value attribute to an entry within exclude scope
|
|
+ 16. Try to add existing value attribute to an entry within another exclude scope
|
|
+ 17. Clean up entries
|
|
+ :expectedresults:
|
|
+ 1. Success
|
|
+ 2. Success
|
|
+ 3. Success
|
|
+ 4. Success
|
|
+ 5. Should raise CONSTRAINT_VIOLATION
|
|
+ 6. Success
|
|
+ 7. Success
|
|
+ 8. Success
|
|
+ 9. Success
|
|
+ 10. Should raise CONSTRAINT_VIOLATION
|
|
+ 11. Success
|
|
+ 12. Success
|
|
+ 13. Success
|
|
+ 14. Should raise CONSTRAINT_VIOLATION
|
|
+ 15. Success
|
|
+ 16. Success
|
|
+ 17. Success
|
|
+ """
|
|
+ log.info('Setup attribute uniqueness plugin')
|
|
+ attruniq = AttributeUniquenessPlugin(topology_st.standalone, dn="cn=attruniq,cn=plugins,cn=config")
|
|
+ attruniq.create(properties={'cn': 'attruniq'})
|
|
+ attruniq.add_unique_attribute('telephonenumber')
|
|
+ attruniq.add_unique_subtree(DEFAULT_SUFFIX)
|
|
+ attruniq.enable_all_subtrees()
|
|
+ attruniq.enable()
|
|
+ topology_st.standalone.restart()
|
|
+
|
|
+ log.info('Create subtrees container')
|
|
+ containers = nsContainers(topology_st.standalone, DEFAULT_SUFFIX)
|
|
+ cont1 = containers.create(properties={'cn': EXCLUDED_CONTAINER_CN})
|
|
+ cont2 = containers.create(properties={'cn': EXCLUDED_BIS_CONTAINER_CN})
|
|
+ cont3 = containers.create(properties={'cn': ENFORCED_CONTAINER_CN})
|
|
+
|
|
+ log.info('Create test users')
|
|
+ users = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
|
+ rdn='cn={}'.format(ENFORCED_CONTAINER_CN))
|
|
+ users_excluded = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
|
+ rdn='cn={}'.format(EXCLUDED_CONTAINER_CN))
|
|
+ users_excluded2 = UserAccounts(topology_st.standalone, DEFAULT_SUFFIX,
|
|
+ rdn='cn={}'.format(EXCLUDED_BIS_CONTAINER_CN))
|
|
+
|
|
+ user1 = users.create(properties={'cn': USER_1_CN,
|
|
+ 'uid': USER_1_CN,
|
|
+ 'sn': USER_1_CN,
|
|
+ 'uidNumber': '1',
|
|
+ 'gidNumber': '11',
|
|
+ 'homeDirectory': '/home/{}'.format(USER_1_CN)})
|
|
+ user2 = users.create(properties={'cn': USER_2_CN,
|
|
+ 'uid': USER_2_CN,
|
|
+ 'sn': USER_2_CN,
|
|
+ 'uidNumber': '2',
|
|
+ 'gidNumber': '22',
|
|
+ 'homeDirectory': '/home/{}'.format(USER_2_CN)})
|
|
+ user3 = users_excluded.create(properties={'cn': USER_3_CN,
|
|
+ 'uid': USER_3_CN,
|
|
+ 'sn': USER_3_CN,
|
|
+ 'uidNumber': '3',
|
|
+ 'gidNumber': '33',
|
|
+ 'homeDirectory': '/home/{}'.format(USER_3_CN)})
|
|
+ user4 = users_excluded2.create(properties={'cn': USER_4_CN,
|
|
+ 'uid': USER_4_CN,
|
|
+ 'sn': USER_4_CN,
|
|
+ 'uidNumber': '4',
|
|
+ 'gidNumber': '44',
|
|
+ 'homeDirectory': '/home/{}'.format(USER_4_CN)})
|
|
+
|
|
+ UNIQUE_VALUE = '1234'
|
|
+
|
|
+ try:
|
|
+ log.info('Create user with unique attribute')
|
|
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Add exclude subtree')
|
|
+ attruniq.add_exclude_subtree(EXCLUDED_CONTAINER_DN)
|
|
+ topology_st.standalone.restart()
|
|
+
|
|
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Verify an entry with same attribute value can be added within exclude subtree')
|
|
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Cleanup unique attribute values')
|
|
+ user1.remove_all('telephonenumber')
|
|
+ user3.remove_all('telephonenumber')
|
|
+
|
|
+ log.info('Add a unique value to an entry in excluded scope')
|
|
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Verify the same value can be added to an entry within uniqueness scope')
|
|
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user1.present('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Verify that yet another same value cannot be added to another entry within uniqueness scope')
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Cleanup unique attribute values')
|
|
+ user1.remove_all('telephonenumber')
|
|
+ user3.remove_all('telephonenumber')
|
|
+
|
|
+ log.info('Add another exclude subtree')
|
|
+ attruniq.add_exclude_subtree(EXCLUDED_BIS_CONTAINER_DN)
|
|
+ topology_st.standalone.restart()
|
|
+
|
|
+ user1.add('telephonenumber', UNIQUE_VALUE)
|
|
+ log.info('Verify an already used attribute value cannot be added within the same subtree')
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ user2.add('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ log.info('Verify an already used attribute can be added to an entry in exclude scope')
|
|
+ user3.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user3.present('telephonenumber', UNIQUE_VALUE)
|
|
+ user4.add('telephonenumber', UNIQUE_VALUE)
|
|
+ assert user4.present('telephonenumber', UNIQUE_VALUE)
|
|
+
|
|
+ finally:
|
|
+ log.info('Clean up users, containers and attribute uniqueness plugin')
|
|
+ user1.delete()
|
|
+ user2.delete()
|
|
+ user3.delete()
|
|
+ user4.delete()
|
|
+ cont1.delete()
|
|
+ cont2.delete()
|
|
+ cont3.delete()
|
|
+ attruniq.disable()
|
|
+ attruniq.delete()
|
|
+
|
|
+
|
|
+def test_matchingrule_attr(topology_st):
|
|
+ """ Test list extension MR attribute. Check for "cn" using CES (versus it
|
|
+ being defined as CIS)
|
|
+
|
|
+ :id: 5cde4342-6fa3-4225-b23d-0af918981075
|
|
+ :setup: Standalone instance
|
|
+ :steps:
|
|
+ 1. Setup and enable attribute uniqueness plugin to use CN attribute
|
|
+ with a matching rule of CaseExactMatch.
|
|
+ 2. Add user with CN value is lowercase
|
|
+ 3. Add second user with same lowercase CN which should be rejected
|
|
+ 4. Add second user with same CN value but with mixed case
|
|
+ 5. Modify second user replacing CN value to lc which should be rejected
|
|
+
|
|
+ :expectedresults:
|
|
+ 1. Success
|
|
+ 2. Success
|
|
+ 3. Success
|
|
+ 4. Success
|
|
+ 5. Success
|
|
+ """
|
|
+
|
|
+ inst = topology_st.standalone
|
|
+
|
|
+ attruniq = AttributeUniquenessPlugin(inst,
|
|
+ dn="cn=attribute uniqueness,cn=plugins,cn=config")
|
|
+ attruniq.add_unique_attribute('cn:CaseExactMatch:')
|
|
+ attruniq.enable_all_subtrees()
|
|
+ attruniq.enable()
|
|
+ inst.restart()
|
|
+
|
|
+ users = UserAccounts(inst, DEFAULT_SUFFIX)
|
|
+ users.create(properties={'cn': "common_name",
|
|
+ 'uid': "uid_name",
|
|
+ 'sn': "uid_name",
|
|
+ 'uidNumber': '1',
|
|
+ 'gidNumber': '11',
|
|
+ 'homeDirectory': '/home/uid_name'})
|
|
+
|
|
+ log.info('Add entry with the exact CN value which should be rejected')
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ users.create(properties={'cn': "common_name",
|
|
+ 'uid': "uid_name2",
|
|
+ 'sn': "uid_name2",
|
|
+ 'uidNumber': '11',
|
|
+ 'gidNumber': '111',
|
|
+ 'homeDirectory': '/home/uid_name2'})
|
|
+
|
|
+ log.info('Add entry with the mixed case CN value which should be allowed')
|
|
+ user = users.create(properties={'cn': "Common_Name",
|
|
+ 'uid': "uid_name2",
|
|
+ 'sn': "uid_name2",
|
|
+ 'uidNumber': '11',
|
|
+ 'gidNumber': '111',
|
|
+ 'homeDirectory': '/home/uid_name2'})
|
|
+
|
|
+ log.info('Mod entry with exact case CN value which should be rejected')
|
|
+ with pytest.raises(ldap.CONSTRAINT_VIOLATION):
|
|
+ user.replace('cn', 'common_name')
|
|
diff --git a/ldap/servers/plugins/uiduniq/uid.c b/ldap/servers/plugins/uiduniq/uid.c
|
|
index 15cf88477..5a0d61b86 100644
|
|
--- a/ldap/servers/plugins/uiduniq/uid.c
|
|
+++ b/ldap/servers/plugins/uiduniq/uid.c
|
|
@@ -1179,6 +1179,10 @@ preop_modify(Slapi_PBlock *pb)
|
|
for (; mods && *mods; mods++) {
|
|
mod = *mods;
|
|
for (i = 0; attrNames && attrNames[i]; i++) {
|
|
+ char *attr_match = strchr(attrNames[i], ':');
|
|
+ if (attr_match != NULL) {
|
|
+ attr_match[0] = '\0';
|
|
+ }
|
|
if ((slapi_attr_type_cmp(mod->mod_type, attrNames[i], 1) == 0) && /* mod contains target attr */
|
|
(mod->mod_op & LDAP_MOD_BVALUES) && /* mod is bval encoded (not string val) */
|
|
(mod->mod_bvalues && mod->mod_bvalues[0]) && /* mod actually contains some values */
|
|
@@ -1187,6 +1191,9 @@ preop_modify(Slapi_PBlock *pb)
|
|
{
|
|
addMod(&checkmods, &checkmodsCapacity, &modcount, mod);
|
|
}
|
|
+ if (attr_match != NULL) {
|
|
+ attr_match[0] = ':';
|
|
+ }
|
|
}
|
|
}
|
|
if (modcount == 0) {
|
|
diff --git a/ldap/servers/slapd/plugin_mr.c b/ldap/servers/slapd/plugin_mr.c
|
|
index 6cf88b7de..f40c9a39b 100644
|
|
--- a/ldap/servers/slapd/plugin_mr.c
|
|
+++ b/ldap/servers/slapd/plugin_mr.c
|
|
@@ -624,7 +624,7 @@ attempt_mr_filter_create(mr_filter_t *f, struct slapdplugin *mrp, Slapi_PBlock *
|
|
int rc;
|
|
IFP mrf_create = NULL;
|
|
f->mrf_match = NULL;
|
|
- pblock_init(pb);
|
|
+ slapi_pblock_init(pb);
|
|
if (!(rc = slapi_pblock_set(pb, SLAPI_PLUGIN, mrp)) &&
|
|
!(rc = slapi_pblock_get(pb, SLAPI_PLUGIN_MR_FILTER_CREATE_FN, &mrf_create)) &&
|
|
mrf_create != NULL &&
|
|
diff --git a/ldap/servers/slapd/str2filter.c b/ldap/servers/slapd/str2filter.c
|
|
index 9fdc500f7..5620b7439 100644
|
|
--- a/ldap/servers/slapd/str2filter.c
|
|
+++ b/ldap/servers/slapd/str2filter.c
|
|
@@ -344,6 +344,14 @@ str2simple(char *str, int unescape_filter)
|
|
return NULL; /* error */
|
|
} else {
|
|
f->f_choice = LDAP_FILTER_EXTENDED;
|
|
+ if (f->f_mr_oid) {
|
|
+ /* apply the MR indexers */
|
|
+ rc = plugin_mr_filter_create(&f->f_mr);
|
|
+ if (rc) {
|
|
+ slapi_filter_free(f, 1);
|
|
+ return NULL; /* error */
|
|
+ }
|
|
+ }
|
|
}
|
|
} else if (str_find_star(value) == NULL) {
|
|
f->f_choice = LDAP_FILTER_EQUALITY;
|
|
--
|
|
2.49.0
|
|
|