From 4e9a904048317e7dab4399ae9b4629251a794746 Mon Sep 17 00:00:00 2001 From: Thierry Bordaz Date: Mon, 9 Oct 2023 15:07:39 +0200 Subject: [PATCH] Issue 5989 - RFE support of inChain Matching Rule Bug description: Computation of membership (like 'memberof') is a common issue. The issue is more expensive to solve when there are nested membership. For example "gives me all the groups this entry belongs to" or "gives me all subordinates having this manager". Either the LDAP client computes the values or dedicated plugin (like 'memberof') maintains direct membership attribute for the LDAP client. InChain Matching Rule allow a LDAP client to request the server to compute this membership. Fix description: The implementation is designed https://www.port389.org/docs/389ds/design/matching-rule-in-chain.html relates: #5989 Reviewed by: William Brown, Mark Reynolds, Pierre Rogier (Thanks !) --- Makefile.am | 1 + .../tests/suites/filter/inchain_test.py | 815 ++++++++++++++++++ ldap/ldif/template-dse-minimal.ldif.in | 18 + ldap/ldif/template-dse.ldif.in | 18 + ldap/servers/plugins/syntaxes/inchain.c | 416 +++++++++ ldap/servers/slapd/back-ldbm/filterindex.c | 36 +- ldap/servers/slapd/back-ldbm/index.c | 4 + ldap/servers/slapd/slap.h | 2 + ldap/servers/slapd/slapi-memberof.c | 32 +- 9 files changed, 1324 insertions(+), 18 deletions(-) create mode 100644 dirsrvtests/tests/suites/filter/inchain_test.py create mode 100644 ldap/servers/plugins/syntaxes/inchain.c diff --git a/Makefile.am b/Makefile.am index c448694c96..e402378b7e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1738,6 +1738,7 @@ libsyntax_plugin_la_SOURCES = ldap/servers/plugins/syntaxes/bin.c \ ldap/servers/plugins/syntaxes/facsimile.c \ ldap/servers/plugins/syntaxes/guide.c \ ldap/servers/plugins/syntaxes/int.c \ + ldap/servers/plugins/syntaxes/inchain.c \ ldap/servers/plugins/syntaxes/nameoptuid.c \ ldap/servers/plugins/syntaxes/numericstring.c \ ldap/servers/plugins/syntaxes/phonetic.c \ diff --git a/dirsrvtests/tests/suites/filter/inchain_test.py b/dirsrvtests/tests/suites/filter/inchain_test.py new file mode 100644 index 0000000000..4af71e7a2c --- /dev/null +++ b/dirsrvtests/tests/suites/filter/inchain_test.py @@ -0,0 +1,815 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2019 RED Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK ---- + +import pytest, os, re +from lib389.tasks import * +from lib389.utils import * +from ldap import SCOPE_SUBTREE, ALREADY_EXISTS + +from lib389._constants import DEFAULT_SUFFIX, PW_DM, PLUGIN_MEMBER_OF +from lib389.topologies import topology_st as topo +from lib389.plugins import MemberOfPlugin + +from lib389.idm.user import UserAccount, UserAccounts +from lib389.idm.account import Accounts +from lib389.idm.account import Anonymous + +INCHAIN_OID = "1.2.840.113556.1.4.1941" + +pytestmark = pytest.mark.tier0 + +@pytest.fixture(scope="function") +def provision_inchain(topo): + """fixture that provision a hierachical tree + with 'manager' membership relation + + 3_1 + |__ 2_1 + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + | | + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + | + |__ 2_2 + |__ 1_3 + | |__ 300 + | |__ 301 + | |__ 302 + | |__ 303 + | |__ 304 + | + |__ 1_4 + |__ 400 + |__ 401 + |__ 402 + |__ 403 + |__ 404 + """ + user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + try: + manager_lvl_3_1 = user.create_test_user(uid=31) + except ALREADY_EXISTS: + manager_lvl_3_1 = None + pass + + try: + manager_lvl_2_1 = user.create_test_user(uid=21) + if manager_lvl_3_1: + manager_lvl_2_1.set("manager", manager_lvl_3_1.dn) + else: + manager_lvl_2_1.set("manager", "uid=test_user_31,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_2_1 = None + pass + + try: + manager_lvl_2_2 = user.create_test_user(uid=22) + if manager_lvl_3_1: + manager_lvl_2_2.set("manager", manager_lvl_3_1.dn) + else: + manager_lvl_2_2.set("manager", "uid=test_user_31,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_2_2 = None + pass + + try: + manager_lvl_1_1 = user.create_test_user(uid=11) + if manager_lvl_2_1: + manager_lvl_1_1.set("manager", manager_lvl_2_1.dn) + else: + manager_lvl_1_1.set("manager", "uid=test_user_21,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_1 = None + pass + + try: + manager_lvl_1_2 = user.create_test_user(uid=12) + if manager_lvl_2_1: + manager_lvl_1_2.set("manager", manager_lvl_2_1.dn) + else: + manager_lvl_1_2.set("manager", "uid=test_user_21,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_2 = None + pass + + try: + manager_lvl_1_3 = user.create_test_user(uid=13) + if manager_lvl_2_2: + manager_lvl_1_3.set("manager", manager_lvl_2_2.dn) + else: + manager_lvl_1_3.set("manager", "uid=test_user_22,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_3 = None + pass + + try: + manager_lvl_1_4 = user.create_test_user(uid=14) + if manager_lvl_2_2: + manager_lvl_1_4.set("manager", manager_lvl_2_2.dn) + else: + manager_lvl_1_4.set("manager", "uid=test_user_22,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + manager_lvl_1_4 = None + pass + + for i in range(100, 105): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_1: + user1.set("manager", manager_lvl_1_1.dn) + else: + user1.set("manager", "uid=test_user_11,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(200, 205): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_2: + user1.set("manager", manager_lvl_1_2.dn) + else: + user1.set("manager", "uid=test_user_12,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(300, 305): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_3: + user1.set("manager", manager_lvl_1_3.dn) + else: + user1.set("manager", "uid=test_user_13,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + + for i in range(400, 405): + try: + user1 = user.create_test_user(uid=i) + if manager_lvl_1_4: + user1.set("manager", manager_lvl_1_4.dn) + else: + user1.set("manager", "uid=test_user_14,ou=People,%s" % DEFAULT_SUFFIX) + except ALREADY_EXISTS: + pass + +def check_subordinates(topo, uid, expected): + """Test filter can search attributes + + :id: 9a1b0a4b-111c-4105-866d-4288f143ee07 + :setup: Standalone instance + :steps: + 1. Add test entry + 2. make search + :expectedresults: + 1. Entry should be added + 2. Operation should succeed + """ + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s" % manager) + + subordinates = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + found = [] + for sub in subordinates: + p =re.compile("uid=(.*),ou.*$") + res = p.search(sub.dn) + found.append(res.group(1)) + topo.standalone.log.info("Subordinate found : %s" % res.group(1)) + + + for sub in expected: + assert sub in found + + for sub in found: + assert sub in expected + + +def test_manager_lvl_1(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 1 manager + + :id: 193040ef-861e-41bd-84c7-c07a53a74e18 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_4 entry + |__ 1_4 + |__ 400 + |__ 401 + |__ 402 + |__ 403 + |__ 404 + + 3. Check subordinates of 1_3 entry + |__ 1_3 + | |__ 300 + | |__ 301 + | |__ 302 + | |__ 303 + | |__ 304 + + 4. Check subordinates of 1_2 entry + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + + 5. Check subordinates of 1_1 entry + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + 3. found subordinates should match expected ones + 4. found subordinates should match expected ones + 5. found subordinates should match expected ones + """ + + # Check subordinates of user_14 + uid = "test_user_14" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_13 + uid = "test_user_13" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_12 + uid = "test_user_12" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_11 + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_manager_lvl_2(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 2 manager + + :id: d0c98211-3b90-4764-913d-f55ea5479029 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 2_1 entry + |__ 2_1 + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + | | + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + + 3. Check subordinates of 2_2 entry + | + |__ 2_2 + |__ 1_3 + | |__ 300 + | |__ 301 + | |__ 302 + | |__ 303 + | |__ 304 + | + |__ 1_4 + |__ 400 + |__ 401 + |__ 402 + |__ 403 + |__ 404 + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + 3. found subordinates should match expected ones + """ + + # Check subordinates of user_22 + uid = "test_user_22" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_14 and below + uid_expected = "test_user_14" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_13 and below + uid_expected = "test_user_13" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # Check subordinates of user_21 + uid = "test_user_21" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_12 and below + uid_expected = "test_user_12" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_11 and below + uid_expected = "test_user_11" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_manager_lvl_3(topo, provision_inchain): + """Test that it succeeds to retrieve the subordinate + of level 3 manager + + :id: d3708a39-7901-4c88-b4af-272aed2aa846 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 3_1 entry + + 3_1 + |__ 2_1 + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + | | + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + | + |__ 2_2 + |__ 1_3 + | |__ 300 + | |__ 301 + | |__ 302 + | |__ 303 + | |__ 304 + | + |__ 1_4 + |__ 400 + |__ 401 + |__ 402 + |__ 403 + |__ 404 + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_31 + uid = "test_user_31" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + + # it contains user_22 and below + uid_expected = "test_user_22" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + + # it contains user_14 and below + uid_expected = "test_user_14" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(400, 405): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_13 and below + uid_expected = "test_user_13" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(300, 305): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + + # it contains user_21 and below + uid_expected = "test_user_21" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + + # it contains user_12 and below + uid_expected = "test_user_12" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(200, 205): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + # it contains user_11 and below + uid_expected = "test_user_11" + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + expected.append(uid_expected) + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_recompute_del(topo, provision_inchain): + """Test that if we delete a subordinate + the subordinate list is correctly updated + + :id: 6865876d-64b5-41a2-ae6e-4453aae5caab + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_1 entry + + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + + 3. Delete user_100 + 4. Check subordinates of 1_1 entry + | |__ 1_1 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_11 + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + del_dn = "uid=test_user_100,ou=People,%s" % (DEFAULT_SUFFIX) + user = UserAccount(topo.standalone, del_dn) + topo.standalone.log.info("Delete: %s" % del_dn) + user.delete() + + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(101, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + +def test_recompute_add(topo, provision_inchain, request): + """Test that if we add a subordinate + the subordinate list is correctly updated + + :id: 60d10233-37c2-400b-ac6e-d29b68706216 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. Check subordinates of 1_1 entry + + | |__ 1_1 + | | |__ 100 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + + 3. Delete user_100 + 4. Check subordinates of 1_1 entry + | |__ 1_1 + | | |__ 101 + | | |__ 102 + | | |__ 103 + | | |__ 104 + :expectedresults: + 1. provisioning done + 2. found subordinates should match expected ones + """ + + # Check subordinates of user_11 + uid = "test_user_11" + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 105): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + # add a new subordinate of user_11 + users = UserAccounts(topo.standalone, DEFAULT_SUFFIX) + user_added = users.create_test_user(uid=105) + topo.standalone.log.info("Add: %s" % user_added.dn) + user_added.set("manager", "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX)) + + topo.standalone.log.info("Subordinate of uid=%s" % uid) + expected = [] + for i in range(100, 106): + uid_expected = "test_user_%s" % i + expected.append(uid_expected) + topo.standalone.log.info("Subordinate expected: %s" % uid_expected) + + check_subordinates(topo, uid, expected) + + def fin(): + user_added.delete() + + request.addfinalizer(fin) + + +def test_anonymous_inchain(topo, provision_inchain): + """Test that anonymous connection can not + retrieve subordinates + + :id: d6c41cc1-7c36-4a3f-bcdf-f479c12310e2 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. bound anonymous + 3. Check subordinates of 1_2 entry is empty although hierarchy + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + + :expectedresults: + 1. provisioning done + 2. succeed + 3. succeeds but 0 subordinates + """ + + # create an anonymous connection + topo.standalone.log.info("Bind as anonymous user") + conn = Anonymous(topo.standalone).bind() + + # Check that there are no subordinates of test_user_12 + uid = "test_user_12" + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s on anonymous connection" % manager) + + subordinates = conn.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + assert len(subordinates) == 0 + + # Check the ACI right failure + assert topo.standalone.ds_error_log.match('.*inchain - Requestor is not allowed to use InChain Matching rule$') + +def test_authenticated_inchain(topo, provision_inchain, request): + """Test that bound connection can not + retrieve subordinates (only DM is allowed by default) + + :id: 17ebee0d-86e2-4f0d-95fe-8a4cf566a493 + :setup: Standalone instance + :steps: + 1. fixture provision a hierachical tree + 2. create a test user + 3. create a bound connection + 4. Check subordinates of 1_2 entry is empty although hierarchy + | |__ 1_2 + | | |__ 200 + | | |__ 201 + | | |__ 202 + | | |__ 203 + | | |__ 204 + + :expectedresults: + 1. provisioning done + 2. succeed + 3. succeed + 4. succeeds but 0 subordinates + """ + + # create a user + RDN = "test_bound_user" + test_user = UserAccount(topo.standalone, "uid=%s,ou=People,%s" % (RDN, DEFAULT_SUFFIX)) + test_user.create(properties={ + 'uid': RDN, + 'cn': RDN, + 'sn': RDN, + 'userPassword': "password", + 'uidNumber' : '1000', + 'gidNumber' : '2000', + 'homeDirectory' : '/home/inchain', + }) + + # create a bound connection + conn = test_user.bind("password") + + # Check that there are no subordinates of test_user_12 + uid = "test_user_12" + manager = "uid=%s,ou=People,%s" % (uid, DEFAULT_SUFFIX) + topo.standalone.log.info("Subordinate of manager %s on bound connection" % manager) + + subordinates = conn.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(manager:%s:=%s)" % (INCHAIN_OID, manager)) + assert len(subordinates) == 0 + + # Check the ACI right failure + assert topo.standalone.ds_error_log.match('.*inchain - Requestor is not allowed to use InChain Matching rule$') + + def fin(): + test_user.delete() + + request.addfinalizer(fin) + + +def _create_user(topology_st, ext): + user_dn = "uid=%s,ou=People,%s" % (ext, DEFAULT_SUFFIX) + topology_st.standalone.add_s(Entry((user_dn, { + 'objectclass': 'top extensibleObject'.split(), + 'uid': ext + }))) + topology_st.standalone.log.info("Create user %s" % user_dn) + return ensure_bytes(user_dn) + +def _create_group(topology_st, ext): + group_dn = "ou=%s,ou=People,%s" % (ext, DEFAULT_SUFFIX) + topology_st.standalone.add_s(Entry((group_dn, { + 'objectclass': 'top groupOfNames extensibleObject'.split(), + 'ou': ext, + 'cn': ext + }))) + topology_st.standalone.log.info("Create group %s" % group_dn) + return ensure_bytes(group_dn) + +def test_reuse_memberof(topo, request): + """Check that slapi_memberof successfully + compute the membership either using 'memberof' attribute + or either recomputing it. + + :id: e52bd21a-3ff6-493f-9ec5-8cf4e76696a0 + :setup: Standalone instance + :steps: + 1. Enable the plugin + 2. Create a user belonging in cascade to 3 groups + 3. Check that slapi_member re-computes membership + 4. Check that slapi_member retrieve membership from memberof + :expectedresults: + 1. Success + 2. Success + 3. Success + 4. Success + """ + + # enable the plugin + topo.standalone.log.info("Enable MemberOf plugin") + topo.standalone.plugins.enable(name=PLUGIN_MEMBER_OF) + topo.standalone.restart() + + # Create a user belonging to 3 goups + # in cascade + user1 = _create_user(topo, 'user1') + + group1 = _create_group(topo, 'group1') + mods = [(ldap.MOD_ADD, 'member', user1)] + topo.standalone.modify_s(ensure_str(group1), mods) + + group2 = _create_group(topo, 'group2') + mods = [(ldap.MOD_ADD, 'member', group1)] + topo.standalone.modify_s(ensure_str(group2), mods) + + group3 = _create_group(topo, 'group3') + mods = [(ldap.MOD_ADD, 'member', group2)] + topo.standalone.modify_s(ensure_str(group3), mods) + + # Call slapi_member that does *not* reuse the memberof + # because of 'memberOfEntryScope' not being set + topo.standalone.log.info("Groups that %s is memberof" % ensure_str(user1)) + + topo.standalone.config.set('nsslapd-errorlog-level', '65536') # plugin logging + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, ensure_str(user1))) + topo.standalone.config.set('nsslapd-errorlog-level', '0') + for sub in memberof: + topo.standalone.log.info("memberof found : %s" % sub.dn) + assert sub.dn in [ensure_str(group1), ensure_str(group2), ensure_str(group3)] + assert topo.standalone.ds_error_log.match('.*sm_compare_memberof_config: fails because requested include scope is not empty.*') + assert not topo.standalone.ds_error_log.match('.*slapi_memberof - sm_compare_memberof_config: succeeds. requested options match config.*') + + # Call slapi_member that does reuse the memberof + # because of 'memberOfEntryScope' being set + memberof = MemberOfPlugin(topo.standalone) + memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) + topo.standalone.restart() + topo.standalone.config.set('nsslapd-errorlog-level', '65536') # plugin logging + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, ensure_str(user1))) + topo.standalone.config.set('nsslapd-errorlog-level', '0') + for sub in memberof: + topo.standalone.log.info("memberof found : %s" % sub.dn) + assert sub.dn in [ensure_str(group1), ensure_str(group2), ensure_str(group3)] + assert topo.standalone.ds_error_log.match('.*slapi_memberof - sm_compare_memberof_config: succeeds. requested options match config.*') + + def fin(): + topo.standalone.delete_s(ensure_str(user1)) + topo.standalone.delete_s(ensure_str(group1)) + topo.standalone.delete_s(ensure_str(group2)) + topo.standalone.delete_s(ensure_str(group3)) + + request.addfinalizer(fin) + +def test_invalid_assertion(topo): + """Check that with invalid assertion + there is no returned entries + + :id: 0a204b81-e7c0-41a0-97cc-7d9425a603c2 + :setup: Standalone instance + :steps: + 1. Search with invalid assertion '..:=foo' + 2. Search with not existing entry '..:=' + :expectedresults: + 1. Success + 2. Success + """ + topo.standalone.log.info("Search with an invalid assertion") + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=foo)" % (INCHAIN_OID)) + assert len(memberof) == 0 + + topo.standalone.log.info("Search with an none exisiting entry") + + user = "uid=not_existing_entry,ou=People,%s" % (DEFAULT_SUFFIX) + memberof = topo.standalone.search_s(DEFAULT_SUFFIX, SCOPE_SUBTREE, "(member:%s:=%s)" % (INCHAIN_OID, user)) + assert len(memberof) == 0 + +if __name__ == "__main__": + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s -v %s" % CURRENT_FILE) diff --git a/ldap/ldif/template-dse-minimal.ldif.in b/ldap/ldif/template-dse-minimal.ldif.in index 9f7c9929dd..d2b02f8be9 100644 --- a/ldap/ldif/template-dse-minimal.ldif.in +++ b/ldap/ldif/template-dse-minimal.ldif.in @@ -34,6 +34,14 @@ objectclass: top objectclass: nsContainer cn: features +dn: oid=1.2.840.113556.1.4.1941,cn=features,cn=config +objectClass: top +objectClass: directoryServerFeature +oid: 1.2.840.113556.1.4.1941 +cn: InChain Matching Rule +aci: (targetattr != "aci")(version 3.0; acl "InChain Matching Rule"; deny( all ) + userdn = "ldap:///anyone";) + dn: oid=1.3.6.1.4.1.42.2.27.9.5.8,cn=features,cn=config objectClass: top objectClass: directoryServerFeature @@ -439,6 +447,16 @@ nsslapd-plugininitfunc: int_init nsslapd-plugintype: syntax nsslapd-pluginenabled: on +dn: cn=In Chain,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: In Chain +nsslapd-pluginpath: libsyntax-plugin +nsslapd-plugininitfunc: inchain_init +nsslapd-plugintype: syntax +nsslapd-pluginenabled: on + dn: cn=Distinguished Name Syntax,cn=plugins,cn=config objectclass: top objectclass: nsSlapdPlugin diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in index 3361a73fcf..b1736ccc28 100644 --- a/ldap/ldif/template-dse.ldif.in +++ b/ldap/ldif/template-dse.ldif.in @@ -50,6 +50,14 @@ objectclass: top objectclass: nsContainer cn: features +dn: oid=1.2.840.113556.1.4.1941,cn=features,cn=config +objectClass: top +objectClass: directoryServerFeature +oid: 1.2.840.113556.1.4.1941 +cn: InChain Matching Rule +aci: (targetattr != "aci")(version 3.0; acl "InChain Matching Rule"; deny( all ) + userdn = "ldap:///anyone";) + dn: oid=1.3.6.1.4.1.42.2.27.9.5.8,cn=features,cn=config objectClass: top objectClass: directoryServerFeature @@ -498,6 +506,16 @@ nsslapd-plugininitfunc: int_init nsslapd-plugintype: syntax nsslapd-pluginenabled: on +dn: cn=In Chain,cn=plugins,cn=config +objectclass: top +objectclass: nsSlapdPlugin +objectclass: extensibleObject +cn: In Chain +nsslapd-pluginpath: libsyntax-plugin +nsslapd-plugininitfunc: inchain_init +nsslapd-plugintype: syntax +nsslapd-pluginenabled: on + dn: cn=Distinguished Name Syntax,cn=plugins,cn=config objectclass: top objectclass: nsSlapdPlugin diff --git a/ldap/servers/plugins/syntaxes/inchain.c b/ldap/servers/plugins/syntaxes/inchain.c new file mode 100644 index 0000000000..52d0c49946 --- /dev/null +++ b/ldap/servers/plugins/syntaxes/inchain.c @@ -0,0 +1,416 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2023 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* inchain.c - in_chain syntax routines + see https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-adts/1e889adc-b503-4423-8985-c28d5c7d4887 + */ + +#include +#include +#include +#include "syntax.h" +#include "slapi-plugin.h" + +int inchain_filter_ava(Slapi_PBlock *pb, struct berval *bvfilter, Slapi_Value **bvals, int ftype, Slapi_Value **retVal); +int inchain_filter_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value **bvals); +int inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **val, Slapi_Value ***ivals, int ftype); +int inchain_assertion2keys_ava(Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype); +int inchain_assertion2keys_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value ***ivals); +int inchain_validate(struct berval *val); +void inchain_normalize( + Slapi_PBlock *pb, + char *s, + int trim_spaces, + char **alt); + +/* the first name is the official one from RFC 4517 */ +static char *names[] = {"inchain", "inchain", LDAP_MATCHING_RULE_IN_CHAIN_OID, 0}; + +static Slapi_PluginDesc pdesc = {"inchain-matching-rule", VENDOR, DS_PACKAGE_VERSION, + "inchain matching rule plugin"}; + +static const char *inchainMatch_names[] = {"inchainMatch", "1.2.840.113556.1.4.1941", NULL}; + +static struct mr_plugin_def mr_plugin_table[] = { + { + { + "1.2.840.113556.1.4.1941", + NULL, + "inchainMatch", + "The distinguishedNameMatch rule compares an assertion value of the DN " + "syntax to an attribute value of a syntax (e.g., the DN syntax) whose " + "corresponding ASN.1 type is DistinguishedName. " + "The rule evaluates to TRUE if and only if the attribute value and the " + "assertion value have the same number of relative distinguished names " + "and corresponding relative distinguished names (by position) are the " + "same. A relative distinguished name (RDN) of the assertion value is " + "the same as an RDN of the attribute value if and only if they have " + "the same number of attribute value assertions and each attribute " + "value assertion (AVA) of the first RDN is the same as the AVA of the " + "second RDN with the same attribute type. The order of the AVAs is " + "not significant. Also note that a particular attribute type may " + "appear in at most one AVA in an RDN. Two AVAs with the same " + "attribute type are the same if their values are equal according to " + "the equality matching rule of the attribute type. If one or more of " + "the AVA comparisons evaluate to Undefined and the remaining AVA " + "comparisons return TRUE then the distinguishedNameMatch rule " + "evaluates to Undefined.", + NULL, + 0, + NULL /* dn only for now */ + }, /* matching rule desc */ + { + "inchainMatch-mr", + VENDOR, + DS_PACKAGE_VERSION, + "inchain matching rule plugin"}, /* plugin desc */ + inchainMatch_names, /* matching rule name/oid/aliases */ + NULL, + NULL, + inchain_filter_ava, + NULL, + inchain_values2keys, + inchain_assertion2keys_ava, + NULL, + NULL, + NULL /* mr_nomalise */ + }, +}; + +static size_t mr_plugin_table_size = sizeof(mr_plugin_table) / sizeof(mr_plugin_table[0]); + +static int +matching_rule_plugin_init(Slapi_PBlock *pb) +{ + return syntax_matching_rule_plugin_init(pb, mr_plugin_table, mr_plugin_table_size); +} + +static int +register_matching_rule_plugins(void) +{ + return syntax_register_matching_rule_plugins(mr_plugin_table, mr_plugin_table_size, matching_rule_plugin_init); +} + +static int +inchain_feature_allowed(Slapi_PBlock *pb) +{ + int isroot = 0; + int ldapcode = LDAP_SUCCESS; + + slapi_pblock_get(pb, SLAPI_REQUESTOR_ISROOT, &isroot); + if (!isroot) { + char *dn; + Slapi_Entry *feature = NULL; + + /* Fetch the feature entry and see if the requestor is allowed access. */ + dn = slapi_ch_smprintf("dn: oid=%s,cn=features,cn=config", LDAP_MATCHING_RULE_IN_CHAIN_OID); + if ((feature = slapi_str2entry(dn, 0)) != NULL) { + char *dummy_attr = "1.1"; + Slapi_Backend *be = NULL; + + be = slapi_mapping_tree_find_backend_for_sdn(slapi_entry_get_sdn(feature)); + if (NULL == be) { + ldapcode = LDAP_INSUFFICIENT_ACCESS; + } else { + slapi_pblock_set(pb, SLAPI_BACKEND, be); + ldapcode = slapi_access_allowed(pb, feature, dummy_attr, NULL, SLAPI_ACL_READ); + } + } + + /* If the feature entry does not exist, deny use of the control. Only + * the root DN will be allowed to use the control in this case. */ + if ((feature == NULL) || (ldapcode != LDAP_SUCCESS)) { + ldapcode = LDAP_INSUFFICIENT_ACCESS; + } + slapi_ch_free((void **)&dn); + slapi_entry_free(feature); + } + return (ldapcode); +} + +int +inchain_init(Slapi_PBlock *pb) +{ + int rc; + + slapi_log_err(SLAPI_LOG_PLUGIN, SYNTAX_PLUGIN_SUBSYSTEM, "=> inchain_init\n"); + + rc = slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, + (void *)SLAPI_PLUGIN_VERSION_01); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, + (void *)&pdesc); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_FILTER_AVA, + (void *)inchain_filter_ava); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_FILTER_SUB, + (void *)inchain_filter_sub); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_VALUES2KEYS, + (void *)inchain_values2keys); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA, + (void *)inchain_assertion2keys_ava); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB, + (void *)inchain_assertion2keys_sub); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_NAMES, + (void *)names); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_OID, + (void *)LDAP_MATCHING_RULE_IN_CHAIN_OID); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_VALIDATE, + (void *)inchain_validate); + rc |= slapi_pblock_set(pb, SLAPI_PLUGIN_SYNTAX_NORMALIZE, + (void *)inchain_normalize); + + rc |= register_matching_rule_plugins(); + slapi_log_err(SLAPI_LOG_PLUGIN, SYNTAX_PLUGIN_SUBSYSTEM, "<= inchain_init %d\n", rc); + return (rc); +} + +int +inchain_filter_ava(Slapi_PBlock *pb, struct berval *bvfilter, Slapi_Value **bvals, int ftype, Slapi_Value **retVal) +{ + /* always true because candidate entries are valid */ + /* in theory we should check the filter but with inchain MR + * inchain_values2keys select candidates where membership attribute/value + * are not systematically present in the candidate entry (recursive call) + * this is the reason why this usual check does not apply + */ +#if 0 + int syntax = SYNTAX_CIS | SYNTAX_DN; + return (string_filter_ava(bvfilter, bvals, syntax, ftype, retVal)); +#else + return(0); +#endif +} + +int +inchain_filter_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value **bvals) +{ + return(1); +} + + +static PRIntn memberof_hash_compare_keys(const void *v1, const void *v2) +{ + PRIntn rc; + if (0 == strcasecmp((const char *) v1, (const char *) v2)) { + rc = 1; + } else { + rc = 0; + } + return rc; +} + +static PRIntn memberof_hash_compare_values(const void *v1, const void *v2) +{ + PRIntn rc; + if ((char *) v1 == (char *) v2) { + rc = 1; + } else { + rc = 0; + } + return rc; +} + +/* + * Hashing function using Bernstein's method + */ +static PLHashNumber memberof_hash_fn(const void *key) +{ + PLHashNumber hash = 5381; + unsigned char *x = (unsigned char *)key; + int c; + + while ((c = *x++)){ + hash = ((hash << 5) + hash) ^ c; + } + return hash; +} + +/* allocates the plugin hashtable + * This hash table is used by operation and is protected from + * concurrent operations with the memberof_lock (if not usetxn, memberof_lock + * is not implemented and the hash table will be not used. + * + * The hash table contains all the DN of the entries for which the memberof + * attribute has been computed/updated during the current operation + * + * hash table should be empty at the beginning and end of the plugin callback + */ +PLHashTable *hashtable_new(int usetxn) +{ + if (!usetxn) { + return NULL; + } + + return PL_NewHashTable(1000, + memberof_hash_fn, + memberof_hash_compare_keys, + memberof_hash_compare_values, NULL, NULL); +} +int +inchain_values2keys(Slapi_PBlock *pb, Slapi_Value **vals, Slapi_Value ***ivals, int ftype) +{ + Slapi_MemberOfResult groupvals = {0}; + Slapi_ValueSet *groupdn_vals; + Slapi_Value **result; + int nbvalues; + Slapi_Value *v; + Slapi_MemberOfConfig config = {0}; + Slapi_DN *member_sdn; + Slapi_DN *base_sdn = NULL; + size_t idx = 0; + char *mrTYPE; +#if 0 + char *filter_str; +#endif + char error_msg[1024] = {0}; + int rc; + + slapi_pblock_get(pb, SLAPI_PLUGIN_MR_TYPE, &mrTYPE); + slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &base_sdn); + + if (! slapi_attr_is_dn_syntax_type(mrTYPE)) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requires distinguishedName syntax. AttributeDescription %s is not distinguishedName\n"); + result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *)); + *ivals = result; + return(0); + } + + /* check if the user is allowed to perform inChain matching */ + if (inchain_feature_allowed(pb) != LDAP_SUCCESS) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", "Requestor is not allowed to use InChain Matching rule\n"); + result = (Slapi_Value **)slapi_ch_calloc(1, sizeof(Slapi_Value *)); + *ivals = result; + return(0); + } + + /* it is used only in case of MEMBEROF_REUSE_ONLY of MEMBEROF_REUSE_IF_POSSIBLE + * to reuse potential results from memberof plugin + * So its value is only "memberof" + */ + config.memberof_attr = "memberof"; + config.groupattrs = (char **) slapi_ch_calloc(sizeof(char*), 2); + config.groupattrs[0] = mrTYPE; + config.groupattrs[1] = NULL; + config.subtree_search = PR_FALSE; + config.allBackends = 0; + config.entryScopes = (Slapi_DN **)slapi_ch_calloc(sizeof(Slapi_DN *), 2); + /* only looking in the base search scope */ + config.entryScopes[0] = slapi_sdn_dup((const Slapi_DN *) base_sdn); + + /* no exclusion for inchain */ + config.entryScopeExcludeSubtrees = NULL; + +#if 0 + filter_str = slapi_ch_smprintf("(%s=*)", "manager"); + config.group_filter = slapi_str2filter(filter_str); + + config.group_slapiattrs = (Slapi_Attr **)slapi_ch_calloc(sizeof(Slapi_Attr *), 3); + config.group_slapiattrs[0] = slapi_attr_new(); + config.group_slapiattrs[1] = slapi_attr_new(); + slapi_attr_init(config.group_slapiattrs[0], "manager"); + slapi_attr_init(config.group_slapiattrs[1], "nsuniqueid"); +#endif + config.recurse = PR_TRUE; + config.maxgroups = 0; + config.flag = MEMBEROF_REUSE_IF_POSSIBLE; + config.error_msg = error_msg; + config.errot_msg_lenght = sizeof(error_msg); + + member_sdn = slapi_sdn_new_dn_byval((const char*) vals[0]->bv.bv_val); + rc = slapi_memberof(&config, member_sdn, &groupvals); + if (rc) { + slapi_log_err(SLAPI_LOG_ERR, "inchain", " slapi_memberof fails %d (msg=%s)\n", rc, error_msg); + } +#if 0 + slapi_filter_free(config.group_filter, 1); + slapi_attr_free(&config.group_slapiattrs[0]); + slapi_attr_free(&config.group_slapiattrs[1]); +#endif + groupdn_vals = groupvals.nsuniqueid_vals; + idx = slapi_valueset_first_value(groupdn_vals, &v); + for (; groupdn_vals && v; idx = slapi_valueset_next_value(groupdn_vals, idx, &v)) { + char value[1000]; + strncpy(value, v->bv.bv_val, v->bv.bv_len); + value[v->bv.bv_len] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "inchain", " groupvals = %s\n", value); + + } + +#if 1 + + nbvalues = slapi_valueset_count(groupdn_vals); + result = (Slapi_Value **)slapi_ch_calloc(nbvalues + 1, sizeof(Slapi_Value *)); + for(idx = 0; idx < slapi_valueset_count(groupdn_vals); idx++) { + char value[1000]; + + result[idx] = slapi_value_dup(groupdn_vals->va[idx]); + strncpy(value, result[idx]->bv.bv_val, result[idx]->bv.bv_len); + value[result[idx]->bv.bv_len] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "inchain", "copy key %s \n", value); + } + if (groupvals.dn_vals) { + slapi_valueset_free(groupvals.dn_vals); + groupvals.dn_vals = NULL; + } + if (groupvals.nsuniqueid_vals) { + slapi_valueset_free(groupvals.nsuniqueid_vals); + groupvals.nsuniqueid_vals = NULL; + } + *ivals = result; + return(0); +#else + return (string_values2keys(pb, vals, ivals, SYNTAX_CIS | SYNTAX_DN, + ftype)); +#endif +} + +int +inchain_assertion2keys_ava(Slapi_PBlock *pb, Slapi_Value *val, Slapi_Value ***ivals, int ftype) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_assertion2keys_ava \n"); + return (string_assertion2keys_ava(pb, val, ivals, + SYNTAX_CIS | SYNTAX_DN, ftype)); +} + +int +inchain_assertion2keys_sub(Slapi_PBlock *pb, char *initial, char **any, char * final, Slapi_Value ***ivals) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_assertion2keys_sub \n"); + return (string_assertion2keys_sub(pb, initial, any, final, ivals, + SYNTAX_CIS | SYNTAX_DN)); +} + +int +inchain_validate(struct berval *val) +{ + int rc = 0; /* Assume value is valid */ + + /* A 0 length value is valid for the DN syntax. */ + if (val == NULL) { + rc = 1; + } else if (val->bv_len > 0) { + rc = distinguishedname_validate(val->bv_val, &(val->bv_val[val->bv_len - 1])); + } + + return rc; +} + +void +inchain_normalize( + Slapi_PBlock *pb __attribute__((unused)), + char *s, + int trim_spaces, + char **alt) +{ + slapi_log_err(SLAPI_LOG_ERR, "inchain", "inchain_normalize %s \n", s); + value_normalize_ext(s, SYNTAX_CIS | SYNTAX_DN, trim_spaces, alt); + return; +} diff --git a/ldap/servers/slapd/back-ldbm/filterindex.c b/ldap/servers/slapd/back-ldbm/filterindex.c index 3bcd715840..d498053792 100644 --- a/ldap/servers/slapd/back-ldbm/filterindex.c +++ b/ldap/servers/slapd/back-ldbm/filterindex.c @@ -451,6 +451,7 @@ extensible_candidates( Slapi_PBlock *pb = slapi_pblock_new(); int mrOP = 0; Slapi_Operation *op = NULL; + Connection *conn = NULL; back_txn txn = {NULL}; slapi_log_err(SLAPI_LOG_TRACE, "extensible_candidates", "=> \n"); slapi_pblock_get(glob_pb, SLAPI_TXN, &txn.back_txn_txn); @@ -470,8 +471,11 @@ extensible_candidates( /* set the pb->pb_op to glob_pb->pb_op to catch the abandon req. * in case the operation is interrupted. */ slapi_pblock_get(glob_pb, SLAPI_OPERATION, &op); + slapi_pblock_get(glob_pb, SLAPI_CONNECTION, &conn); /* coverity[var_deref_model] */ slapi_pblock_set(pb, SLAPI_OPERATION, op); + slapi_pblock_set(pb, SLAPI_CONNECTION, conn); + slapi_pblock_set(pb, SLAPI_REQUESTOR_ISROOT, &op->o_isroot); slapi_pblock_get(pb, SLAPI_PLUGIN_MR_INDEX_FN, &mrINDEX); slapi_pblock_get(pb, SLAPI_PLUGIN_OBJECT, &mrOBJECT); @@ -516,16 +520,36 @@ extensible_candidates( } else if (keys == NULL || keys[0] == NULL) { /* no keys */ idl_free(&idl); - idl = idl_allids(be); + if (strcmp(mrOID, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + /* we need to return no candidate else, inchain_filter_ava + * matching all candidates, the search returns invalid results + */ + idl = idl_alloc(0); + } else { + idl = idl_allids(be); + } } else { IDList *idl2 = NULL; struct berval **key; +#define KEY_STR_LGHT 35 /* stollen from nsuniqueid.c UIDSTR_SIZE 35 */ + char key_str[KEY_STR_LGHT + 1]; /* only used for debug logging */ for (key = keys; *key != NULL; ++key) { int unindexed = 0; IDList *idl3 = (mrOP == SLAPI_OP_EQUAL) ? index_read_ext_allids(pb, be, mrTYPE, mrOID, *key, &txn, err, &unindexed, allidslimit) : index_range_read_ext(pb, be, mrTYPE, mrOID, mrOP, *key, NULL, 0, &txn, err, allidslimit); + if (slapi_is_loglevel_set(SLAPI_LOG_FILTER)) { + int lenght_str = key[0]->bv_len; + + if (key[0]->bv_len > KEY_STR_LGHT) { + lenght_str = KEY_STR_LGHT; + } + + strncpy(key_str, key[0]->bv_val, lenght_str); + key_str[lenght_str] = '\0'; + slapi_log_err(SLAPI_LOG_FILTER, "extensible_candidates", "=> idl (%s) = (%d)\n", key_str, idl3->b_ids[0]); + } if (unindexed) { int pr_idx = -1; slapi_pblock_set_flag_operation_notes(pb, SLAPI_OP_NOTE_UNINDEXED); @@ -542,7 +566,12 @@ extensible_candidates( /* first iteration */ idl2 = idl3; } else { - IDList *tmp = idl_intersection(be, idl2, idl3); + IDList *tmp; + if (strcmp(mrOID, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + tmp = idl_union(be, idl2, idl3); + } else { + tmp = idl_intersection(be, idl2, idl3); + } idl_free(&idl2); idl_free(&idl3); idl2 = tmp; @@ -575,8 +604,11 @@ extensible_candidates( } return_idl: op = NULL; + conn = NULL; + /* coverity[var_deref_model] */ slapi_pblock_set(pb, SLAPI_OPERATION, op); + slapi_pblock_set(pb, SLAPI_CONNECTION, conn); slapi_pblock_destroy(pb); slapi_log_err(SLAPI_LOG_TRACE, "extensible_candidates", "<= %lu\n", (u_long)IDL_NIDS(idl)); diff --git a/ldap/servers/slapd/back-ldbm/index.c b/ldap/servers/slapd/back-ldbm/index.c index 5b34a1e879..86bc825feb 100644 --- a/ldap/servers/slapd/back-ldbm/index.c +++ b/ldap/servers/slapd/back-ldbm/index.c @@ -924,6 +924,10 @@ index_read_ext_allids( *err = 0; + if (strcmp(indextype, LDAP_MATCHING_RULE_IN_CHAIN_OID) == 0) { + type = "nsuniqueid"; + indextype = indextype_EQUALITY; + } if (unindexed != NULL) *unindexed = 0; prefix = index_index2prefix(indextype); diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h index 40f5cebb07..aea052983c 100644 --- a/ldap/servers/slapd/slap.h +++ b/ldap/servers/slapd/slap.h @@ -751,6 +751,7 @@ typedef int (*SyntaxEnumFunc)(char **names, Slapi_PluginDesc *plugindesc, void * #define INTEGERORDERINGMATCH_OID "2.5.13.15" /* integerOrderingMatch */ #define INTFIRSTCOMPMATCH_OID "2.5.13.29" /* integerFirstComponentMatch */ #define OIDFIRSTCOMPMATCH_OID "2.5.13.30" /* objectIdentifierFirstComponentMatch */ +#define LDAP_MATCHING_RULE_IN_CHAIN_OID "1.2.840.113556.1.4.1941" /* Names for some commonly used matching rules */ #define DNMATCH_NAME "distinguishedNameMatch" @@ -759,6 +760,7 @@ typedef int (*SyntaxEnumFunc)(char **names, Slapi_PluginDesc *plugindesc, void * #define INTEGERORDERINGMATCH_NAME "integerOrderingMatch" #define INTFIRSTCOMPMATCH_NAME "integerFirstComponentMatch" #define OIDFIRSTCOMPMATCH_NAME "objectIdentifierFirstComponentMatch" +#define LDAP_MATCHING_RULE_IN_CHAIN_NAME "ancestryDNMatch" #define ATTR_STANDARD_STRING "Standard Attribute" #define ATTR_USERDEF_STRING "User Defined Attribute" diff --git a/ldap/servers/slapd/slapi-memberof.c b/ldap/servers/slapd/slapi-memberof.c index 6f39965bbe..f71fd8854b 100644 --- a/ldap/servers/slapd/slapi-memberof.c +++ b/ldap/servers/slapd/slapi-memberof.c @@ -784,12 +784,12 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool int32_t cnt1, cnt2; if ((memberof_config == NULL) || (memberof_config->enabled == PR_FALSE)) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: config not initialized or disabled\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: config not initialized or disabled\n"); return PR_FALSE; } if (enabled_only) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: check the plugin is enabled that is %s\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: check the plugin is enabled that is %s\n", memberof_config->enabled ? "SUCCEEDS" : "FAILS"); if (memberof_config->enabled) { return PR_TRUE; @@ -801,7 +801,7 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool /* Check direct flags */ if ((all_backends != memberof_config->all_backends) || (skip_nested != memberof_config->skip_nested)) { /* If those flags do not match the current set of 'memberof' values is invalid */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails (allbackend %d vs %d, skip_nested %d vs %d)\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails (allbackend %d vs %d, skip_nested %d vs %d)\n", all_backends, memberof_config->all_backends, skip_nested, memberof_config->skip_nested); return PR_FALSE; } @@ -811,7 +811,7 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool */ if ((memberof_attr == NULL) || (memberof_config->memberof_attr == NULL) || (strcasecmp(memberof_attr, memberof_config->memberof_attr))) { /* just be conservative, we should speak about the same attribute */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails memberof attribute differs (require '%s' vs config '%s')\n", memberof_attr ? memberof_attr : "NULL", memberof_config->memberof_attr ? memberof_config->memberof_attr : NULL); @@ -823,12 +823,12 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool */ if (groupattrs == NULL) { /* This is a mandatory parameter */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attributes is empty\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attributes is empty\n"); return PR_FALSE; } for (cnt1 = 0; groupattrs[cnt1]; cnt1++) { if (charray_inlist(memberof_config->groupattrs, groupattrs[cnt1]) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested group attribute '%s' is not configured\n", groupattrs[cnt1]); return PR_FALSE; @@ -837,26 +837,26 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool for (cnt2 = 0; memberof_config->groupattrs && memberof_config->groupattrs[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure groupattrs is not a subset of memberof_config->groupattrs */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested group attributes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested group attributes differs from config\n"); return PR_FALSE; } /* check Include scope that is optional */ if (include_scope == NULL) { if (memberof_config->include_scope) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is empty that differs config\n"); return PR_FALSE; } } else { if (memberof_config->include_scope == NULL) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is not empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope is not empty that differs config\n"); return PR_FALSE; } } /* here include scopes are both NULL or both not NULL */ for (cnt1 = 0; include_scope && include_scope[cnt1]; cnt1++) { if (charray_inlist(memberof_config->include_scope, (char *) slapi_sdn_get_ndn(include_scope[cnt1])) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope (%s) is not in config\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested include scope (%s) is not in config\n", slapi_sdn_get_ndn(include_scope[cnt1])); return PR_FALSE; } @@ -864,26 +864,26 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool for (cnt2 = 0; memberof_config->include_scope && memberof_config->include_scope[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure include_scope is not a subset of memberof_config->include_scope */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested included scopes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested included scopes differs from config\n"); return PR_FALSE; } /* check Exclude scope that is optional */ if (exclude_scope == NULL) { if (memberof_config->exclude_scope) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is empty that differs config\n"); return PR_FALSE; } } else { if (memberof_config->exclude_scope == NULL) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is not empty that differs config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope is not empty that differs config\n"); return PR_FALSE; } } /* here exclude scopes are both NULL or both not NULL */ for (cnt1 = 0; exclude_scope && exclude_scope[cnt1]; cnt1++) { if (charray_inlist(memberof_config->exclude_scope, (char *) slapi_sdn_get_ndn(exclude_scope[cnt1])) == 0) { - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope (%s) is not in config\n", + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because requested exclude scope (%s) is not in config\n", slapi_sdn_get_ndn(exclude_scope[cnt1])); return PR_FALSE; } @@ -891,10 +891,10 @@ sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool for (cnt2 = 0; memberof_config->exclude_scope && memberof_config->exclude_scope[cnt2]; cnt2++); if (cnt1 != cnt2) { /* make sure exclude_scope is not a subset of memberof_config->exclude_scope */ - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested exclude scopes differs from config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: fails because number of requested exclude scopes differs from config\n"); return PR_FALSE; } - slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: succeeds. requested options match config\n"); + slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_compare_memberof_config: succeeds. requested options match config\n"); return PR_TRUE; }