From 87b107cb4f37bc813f32ff4a028485c8e10b209b Mon Sep 17 00:00:00 2001 From: Mark Reynolds Date: Mon, 24 Jul 2023 11:52:19 -0400 Subject: [PATCH] Bump version to 2.3.4-3 Resolves: rhbz#2189954 - RFE Improve reponse time to filters containing 'nsrole' Resolves: rhbz#2189946 - RFE support of slapi_memberof for plugins/core server Resolves: rhbz#1974242 - Paged search impacts performance --- ...hen-a-filter-contains-nsrole-improve.patch | 742 ++ ...E-that-implement-slapi_memberof-5694.patch | 6384 +++++++++++++++++ ...t-empty-and-not-loaded-ns-slapd-high.patch | 33 + ...aged-search-impacts-performance-5838.patch | 611 ++ 389-ds-base.spec | 13 +- 5 files changed, 7781 insertions(+), 2 deletions(-) create mode 100644 0001-Issue-5722-RFE-When-a-filter-contains-nsrole-improve.patch create mode 100644 0002-Issue-5156-RFE-that-implement-slapi_memberof-5694.patch create mode 100644 0003-Issue-5551-Almost-empty-and-not-loaded-ns-slapd-high.patch create mode 100644 0004-Issue-4551-Paged-search-impacts-performance-5838.patch diff --git a/0001-Issue-5722-RFE-When-a-filter-contains-nsrole-improve.patch b/0001-Issue-5722-RFE-When-a-filter-contains-nsrole-improve.patch new file mode 100644 index 0000000..01cdac0 --- /dev/null +++ b/0001-Issue-5722-RFE-When-a-filter-contains-nsrole-improve.patch @@ -0,0 +1,742 @@ +From ca4f4e7712bd7960970f1b10fc859c8bc4679c18 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Tue, 9 May 2023 15:06:55 +0200 +Subject: [PATCH 1/5] Issue 5722 - RFE When a filter contains 'nsrole', improve + response time by rewriting the filter (#5723) + +Bug description: + 'nsrole' is a virtual attribute and is not indexed. + With a poorly selective filter like below the search may be not indexed + "(&(nsrole=cn=managed_role,cn=suffix)(objectclass=posixAccount)))" + + The RFE is to rewrite the filter component contains 'nsrole' + attribute type. + Rewritten component can then been indexed + +Fix description: + For managed role, it replaces 'nsrole' with 'nsroleDN' + attribute type + + For filtered roled, it replace the 'nsrole' component + with the nsRoleFilter value + +relates: #5722 + +Reviewed by: Pierre Rogier (Thanks) +--- + dirsrvtests/tests/suites/roles/basic_test.py | 257 ++++++++++++++++++- + ldap/servers/plugins/roles/roles_cache.c | 204 +++++++++++++++ + ldap/servers/slapd/back-ldbm/ldbm_attr.c | 67 +---- + ldap/servers/slapd/filter.c | 66 +++++ + ldap/servers/slapd/slapi-plugin.h | 3 + + 5 files changed, 535 insertions(+), 62 deletions(-) + +diff --git a/dirsrvtests/tests/suites/roles/basic_test.py b/dirsrvtests/tests/suites/roles/basic_test.py +index bc227a2d7..13d188d68 100644 +--- a/dirsrvtests/tests/suites/roles/basic_test.py ++++ b/dirsrvtests/tests/suites/roles/basic_test.py +@@ -13,16 +13,22 @@ Importing necessary Modules. + + import logging + import time ++import ldap + import os + import pytest + +-from lib389._constants import PW_DM, DEFAULT_SUFFIX ++from lib389._constants import ErrorLog, PW_DM, DEFAULT_SUFFIX, DEFAULT_BENAME + from lib389.idm.user import UserAccount, UserAccounts + from lib389.idm.organization import Organization + from lib389.idm.organizationalunit import OrganizationalUnit + from lib389.topologies import topology_st as topo + from lib389.idm.role import FilteredRoles, ManagedRoles, NestedRoles + from lib389.idm.domain import Domain ++from lib389.dbgen import dbgen_users ++from lib389.tasks import ImportTask ++from lib389.utils import get_default_db_lib ++from lib389.rewriters import * ++from lib389.backend import Backends + + logging.getLogger(__name__).setLevel(logging.INFO) + log = logging.getLogger(__name__) +@@ -504,6 +510,255 @@ def test_vattr_on_managed_role(topo, request): + + request.addfinalizer(fin) + ++def test_managed_and_filtered_role_rewrite(topo, request): ++ """Test that filter components containing 'nsrole=xxx' ++ are reworked if xxx is either a filtered role or a managed ++ role. ++ ++ :id: e30ff5ed-4f8b-48db-bb88-66f150fca31f ++ :setup: server ++ :steps: ++ 1. Setup nsrole rewriter ++ 2. Add a 'nsroleDN' indexes for managed roles ++ 3. Create an 90K ldif files ++ This is large so that unindex search will last long ++ 4. import/restart the instance ++ 5. Create a managed role and add 4 entries in that role ++ 6. Check that a search 'nsrole=managed_role' is fast ++ 7. Create a filtered role that use an indexed attribute (givenName) ++ 8. Check that a search 'nsrole=filtered_role' is fast ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ 5. Operation should succeed ++ 6. Operation should succeed ++ 7. Operation should succeed ++ 8. Operation should succeed ++ """ ++ # Setup nsrole rewriter ++ rewriters = Rewriters(topo.standalone) ++ rewriter = rewriters.ensure_state(properties={"cn": "nsrole", "nsslapd-libpath": 'libroles-plugin'}) ++ try: ++ rewriter.add('nsslapd-filterrewriter', "role_nsRole_filter_rewriter") ++ except: ++ pass ++ ++ # Create an index for nsRoleDN that is used by managed role ++ attrname = 'nsRoleDN' ++ backends = Backends(topo.standalone) ++ backend = backends.get(DEFAULT_BENAME) ++ indexes = backend.get_indexes() ++ try: ++ index = indexes.create(properties={ ++ 'cn': attrname, ++ 'nsSystemIndex': 'false', ++ 'nsIndexType': ['eq', 'pres'] ++ }) ++ except: ++ pass ++ ++ # Build LDIF file ++ bdb_values = { ++ 'wait30': 30 ++ } ++ ++ # Note: I still sometime get failure with a 60s timeout so lets use 90s ++ mdb_values = { ++ 'wait30': 90 ++ } ++ ++ if get_default_db_lib() == 'bdb': ++ values = bdb_values ++ else: ++ values = mdb_values ++ ++ ldif_dir = topo.standalone.get_ldif_dir() ++ import_ldif = ldif_dir + '/perf_import.ldif' ++ ++ RDN="userNew" ++ PARENT="ou=people,%s" % DEFAULT_SUFFIX ++ dbgen_users(topo.standalone, 90000, import_ldif, DEFAULT_SUFFIX, entry_name=RDN, generic=True, parent=PARENT) ++ ++ # online import ++ import_task = ImportTask(topo.standalone) ++ import_task.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX) ++ import_task.wait(values['wait30']) # If things go wrong import takes a lot longer than this ++ assert import_task.is_complete() ++ ++ # Restart server ++ topo.standalone.restart() ++ ++ # Create Managed role entry ++ managed_roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX) ++ role = managed_roles.create(properties={"cn": 'MANAGED_ROLE'}) ++ ++ # Assign managed role to 4 entries out of the 90K ++ for i in range(1, 5): ++ dn = "uid=%s0000%d,%s" % (RDN, i, PARENT) ++ topo.standalone.modify_s(dn, [(ldap.MOD_REPLACE, 'nsRoleDN', [role.dn.encode()])]) ++ ++ ++ # Now check that search is fast, evaluating only 4 entries ++ search_start = time.time() ++ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) ++ duration = time.time() - search_start ++ log.info("Duration of the search was %f", duration) ++ assert(len(entries) == 4) ++ assert (duration < 1) ++ ++ # Restart server to refresh entrycache ++ topo.standalone.restart() ++ ++ # Create Filtered Role entry ++ # it uses 'givenName' attribute that is indexed (eq) by default ++ filtered_roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX) ++ role = filtered_roles.create(properties={'cn': 'FILTERED_ROLE', 'nsRoleFilter': 'givenName=Technical'}) ++ ++ # Now check that search is fast ++ search_start = time.time() ++ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(nsrole=%s)" % role.dn) ++ duration = time.time() - search_start ++ log.info("Duration of the search was %f", duration) ++ assert (duration < 1) ++ ++ def fin(): ++ topo.standalone.restart() ++ try: ++ managed_roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX) ++ for i in managed_roles.list(): ++ i.delete() ++ filtered_roles = FilteredRoles(topo.standalone, DEFAULT_SUFFIX) ++ for i in filtered_roles.list(): ++ i.delete() ++ except: ++ pass ++ os.remove(import_ldif) ++ ++ request.addfinalizer(fin) ++ ++def test_not_such_entry_role_rewrite(topo, request): ++ """Test that filter components containing 'nsrole=xxx' ++ ,where xxx does not refer to any role definition, ++ replace the component by 'nsuniqueid=-1' ++ ++ :id: b098dda5-fc77-46c4-84a7-5d0c7035bb77 ++ :setup: server ++ :steps: ++ 1. Setup nsrole rewriter ++ 2. Add a 'nsroleDN' indexes for managed roles ++ 3. Create an 90K ldif files ++ This is large so that unindex search will last long ++ 4. import/restart the instance ++ 5. Create a managed role and add 4 entries in that role ++ 6. Enable plugin log level to capture role plugin message ++ 7. Check that a search is fast "(OR(nsrole=managed_role)(nsrole=not_existing_role))" ++ 8. Stop the instance ++ 9. Check that a message like this was logged: replace (nsrole=not_existing_role) by (nsuniqueid=-1) ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ 5. Operation should succeed ++ 6. Operation should succeed ++ 7. Operation should succeed ++ 8. Operation should succeed ++ 9. Operation should succeed ++ """ ++ # Setup nsrole rewriter ++ rewriters = Rewriters(topo.standalone) ++ rewriter = rewriters.ensure_state(properties={"cn": "nsrole", "nsslapd-libpath": 'libroles-plugin'}) ++ try: ++ rewriter.add('nsslapd-filterrewriter', "role_nsRole_filter_rewriter") ++ except: ++ pass ++ ++ # Create an index for nsRoleDN that is used by managed role ++ attrname = 'nsRoleDN' ++ backends = Backends(topo.standalone) ++ backend = backends.get(DEFAULT_BENAME) ++ indexes = backend.get_indexes() ++ try: ++ index = indexes.create(properties={ ++ 'cn': attrname, ++ 'nsSystemIndex': 'false', ++ 'nsIndexType': ['eq', 'pres'] ++ }) ++ except: ++ pass ++ ++ # Build LDIF file ++ bdb_values = { ++ 'wait60': 60 ++ } ++ ++ # Note: I still sometime get failure with a 60s timeout so lets use 90s ++ mdb_values = { ++ 'wait60': 90 ++ } ++ ++ if get_default_db_lib() == 'bdb': ++ values = bdb_values ++ else: ++ values = mdb_values ++ ++ ldif_dir = topo.standalone.get_ldif_dir() ++ import_ldif = ldif_dir + '/perf_import.ldif' ++ ++ RDN="userNew" ++ PARENT="ou=people,%s" % DEFAULT_SUFFIX ++ dbgen_users(topo.standalone, 90000, import_ldif, DEFAULT_SUFFIX, entry_name=RDN, generic=True, parent=PARENT) ++ ++ # online import ++ import_task = ImportTask(topo.standalone) ++ import_task.import_suffix_from_ldif(ldiffile=import_ldif, suffix=DEFAULT_SUFFIX) ++ import_task.wait(values['wait60']) # If things go wrong import takes a lot longer than this ++ assert import_task.is_complete() ++ ++ # Restart server ++ topo.standalone.restart() ++ ++ # Create Managed role entry ++ managed_roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX) ++ role = managed_roles.create(properties={"cn": 'MANAGED_ROLE'}) ++ ++ # Assign managed role to 4 entries out of the 90K ++ for i in range(1, 5): ++ dn = "uid=%s0000%d,%s" % (RDN, i, PARENT) ++ topo.standalone.modify_s(dn, [(ldap.MOD_REPLACE, 'nsRoleDN', [role.dn.encode()])]) ++ ++ # Enable plugin level to check message ++ topo.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN)) ++ ++ # Now check that search is fast, evaluating only 4 entries ++ search_start = time.time() ++ entries = topo.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, "(|(nsrole=%s)(nsrole=cn=not_such_entry_role,%s))" % (role.dn, DEFAULT_SUFFIX)) ++ duration = time.time() - search_start ++ log.info("Duration of the search was %f", duration) ++ assert(len(entries) == 4) ++ assert (duration < 1) ++ ++ # Restart server to refresh entrycache ++ topo.standalone.stop() ++ ++ # Check that when the role does not exist it is translated into 'nsuniqueid=-1' ++ pattern = ".*replace \(nsRole=cn=not_such_entry_role,dc=example,dc=com\) by \(nsuniqueid=-1\).*" ++ assert topo.standalone.ds_error_log.match(pattern) ++ ++ def fin(): ++ topo.standalone.restart() ++ try: ++ managed_roles = ManagedRoles(topo.standalone, DEFAULT_SUFFIX) ++ for i in managed_roles.list(): ++ i.delete() ++ except: ++ pass ++ os.remove(import_ldif) ++ ++ request.addfinalizer(fin) ++ + if __name__ == "__main__": + CURRENT_FILE = os.path.realpath(__file__) + pytest.main("-s -v %s" % CURRENT_FILE) +diff --git a/ldap/servers/plugins/roles/roles_cache.c b/ldap/servers/plugins/roles/roles_cache.c +index 5fd52e7b9..bbed11802 100644 +--- a/ldap/servers/plugins/roles/roles_cache.c ++++ b/ldap/servers/plugins/roles/roles_cache.c +@@ -2131,3 +2131,207 @@ roles_cache_dump(caddr_t data, caddr_t arg __attribute__((unused))) + + return 0; + } ++ ++ ++/* This is an example callback to substitute ++ * attribute type 'from' with 'to' in all ++ * the filter components ++ * example_substitute_type is a callback (FILTER_APPLY_FN) used by slapi_filter_apply ++ * typedef int (FILTER_APPLY_FN)(Slapi_Filter f, void *arg) ++ * To stick to the definition, the callback is defined using 'int' rather 'int32_t' ++ */ ++typedef struct { ++ char *attrtype_from; ++ char *attrtype_to; ++} role_substitute_type_arg_t; ++ ++ ++static void ++_rewrite_nsrole_component(Slapi_Filter *f, role_substitute_type_arg_t *substitute_arg) ++{ ++ char *type; ++ struct berval *bval; ++ char *attrs[3] = {SLAPI_ATTR_OBJECTCLASS, ROLE_FILTER_ATTR_NAME, NULL}; ++ Slapi_Entry *nsrole_entry = NULL; ++ Slapi_DN *sdn = NULL; ++ char *rolefilter = NULL; ++ int rc; ++ char **oc_values = NULL; ++ ++ /* Substitution is only valid if original attribute is NSROLEATTR */ ++ if ((substitute_arg == NULL) || ++ (substitute_arg->attrtype_from == NULL) || ++ (substitute_arg->attrtype_to == NULL)) { ++ return; ++ } ++ if (strcasecmp(substitute_arg->attrtype_from, NSROLEATTR)) { ++ return; ++ } ++ if (slapi_filter_get_choice(f) != LDAP_FILTER_EQUALITY) { ++ /* only equality filter are handled ++ * else it is not possible to retrieve the role ++ * via its DN. ++ * Safety checking, at this point filter component has ++ * already been tested as equality match. ++ */ ++ return; ++ } ++ ++ /* Check that assertion does not refer to a filter/nested role */ ++ if (slapi_filter_get_ava(f, &type, &bval)) { ++ return; ++ } ++ sdn = slapi_sdn_new_dn_byref(bval->bv_val); ++ rc = slapi_search_internal_get_entry(sdn, attrs, &nsrole_entry, roles_get_plugin_identity()); ++ if (rc != LDAP_SUCCESS) { ++ if (rc == LDAP_NO_SUCH_OBJECT) { ++ /* the role does not exist (nsrole=) ++ * that means no entry match this component. To speed up ++ * the built of candidate list we may replace this component ++ * with an indexed component that return empty IDL. ++ * nsuniqueid is indexed and -1 is an invalid value. ++ */ ++ char *empty_IDL_filter; ++ empty_IDL_filter = slapi_ch_smprintf("(%s=-1)", SLAPI_ATTR_UNIQUEID); ++ slapi_filter_replace_strfilter(f, empty_IDL_filter); ++ slapi_log_err(SLAPI_LOG_PLUGIN, ROLES_PLUGIN_SUBSYSTEM, "_rewrite_nsrole_component: replace (%s=%s) by %s\n", ++ substitute_arg->attrtype_from, (char *)slapi_sdn_get_ndn(sdn), empty_IDL_filter); ++ slapi_ch_free_string(&empty_IDL_filter); ++ ++ } ++ goto bail; ++ } ++ oc_values = slapi_entry_attr_get_charray(nsrole_entry, SLAPI_ATTR_OBJECTCLASS); ++ rolefilter = slapi_entry_attr_get_charptr(nsrole_entry, ROLE_FILTER_ATTR_NAME); ++ for (size_t i = 0; oc_values && oc_values[i]; ++i) { ++ if (!strcasecmp(oc_values[i], (char *)"nsSimpleRoleDefinition") || ++ !strcasecmp(oc_values[i], (char *)"nsManagedRoleDefinition")) { ++ /* This is a managed role, okay to rewrite the attribute type */ ++ slapi_filter_changetype(f, substitute_arg->attrtype_to); ++ goto bail; ++ } else if (!strcasecmp(oc_values[i], (char *)"nsFilteredRoleDefinition") && rolefilter) { ++ /* filtered role, okay to rewrite the filter with ++ * the value of the nsRoleFilter ++ */ ++ slapi_filter_replace_strfilter(f, rolefilter); ++ goto bail; ++ } else if (!strcasecmp(oc_values[i], (char *)"nsNestedRoleDefinition")) { ++ /* nested roles can not be rewritten */ ++ goto bail; ++ } ++ } ++ ++bail: ++ slapi_ch_free_string(&rolefilter); ++ slapi_ch_array_free(oc_values); ++ slapi_entry_free(nsrole_entry); ++ slapi_sdn_free(&sdn); ++ return; ++} ++ ++/* It calls _rewrite_nsrole_component for each filter component with ++ * - filter choice LDAP_FILTER_EQUALITY ++ * - filter attribute type nsRole ++ */ ++static int ++role_substitute_type(Slapi_Filter *f, void *arg) ++{ ++ role_substitute_type_arg_t *substitute_arg = (role_substitute_type_arg_t *) arg; ++ char *filter_type; ++ ++ if ((substitute_arg == NULL) || ++ (substitute_arg->attrtype_from == NULL) || ++ (substitute_arg->attrtype_to == NULL)) { ++ return SLAPI_FILTER_SCAN_STOP; ++ } ++ ++ if (slapi_filter_get_choice(f) != LDAP_FILTER_EQUALITY) { ++ /* only equality filter are handled ++ * else it is not possible to retrieve the role ++ * via its DN ++ */ ++ return SLAPI_FILTER_SCAN_CONTINUE; ++ } ++ ++ /* In case this is expected attribute type and assertion ++ * Substitute 'from' by 'to' attribute type in the filter ++ */ ++ if (slapi_filter_get_attribute_type(f, &filter_type) == 0) { ++ if ((strcasecmp(filter_type, substitute_arg->attrtype_from) == 0)) { ++ _rewrite_nsrole_component(f, substitute_arg); ++ } ++ } ++ ++ /* Return continue because we should ++ * substitute 'from' in all filter components ++ */ ++ return SLAPI_FILTER_SCAN_CONTINUE; ++} ++ ++ ++/* ++ * During PRE_SEARCH, rewriters (a.k.a computed attributes) are called ++ * to rewrite some search filter components. ++ * Rewriters callbacks are registered by main and are retrieved from ++ * config entries under cn=rewriters,cn=config. ++ * ++ * In order to register the role rewriter, the following record ++ * is added to the config. ++ * ++ * dn: cn=role,cn=rewriters,cn=config ++ * objectClass: top ++ * objectClass: extensibleObject ++ * cn: role ++ * nsslapd-libpath: /lib/dirsrv/libslapd.so ++ * nsslapd-filterrewriter: role_nsRole_filter_rewriter ++ * ++ * The role rewriter supports: ++ * - 'nsrole' attribute type ++ * - LDAP_FILTER_EQUALITY filter choice ++ * - assertion being a managed/filtered role DN ++ * ++ * - Input '(nsrole=cn=admin1,dc=example,dc=com)' ++ * Output '(nsroleDN=cn=admin1,dc=example,dc=com)' ++ * - Input '(nsrole=cn=SalesManagerFilter,ou=people,dc=example,dc=com)' ++ * Output '(manager=user008762)' ++ * ++ * dn: cn=admin1,dc=example,dc=com ++ * ... ++ * objectClass: nsRoleDefinition ++ * objectClass: nsManagedRoleDefinition ++ * ... ++ * ++ * dn: cn=SalesManagerFilter,ou=people,dc=example,dc=com ++ * ... ++ * objectclass: nsRoleDefinition ++ * objectclass: nsFilteredRoleDefinition ++ * ... ++ * nsRoleFilter: manager=user008762 ++ * ++ * return code (from computed.c:compute_rewrite_search_filter): ++ * -1 : keep looking ++ * 0 : rewrote OK ++ * 1 : refuse to do this search ++ * 2 : operations error ++ */ ++int32_t ++role_nsRole_filter_rewriter(Slapi_PBlock *pb) ++{ ++ Slapi_Filter *clientFilter = NULL; ++ int error_code = 0; ++ int rc; ++ role_substitute_type_arg_t arg; ++ arg.attrtype_from = NSROLEATTR; ++ arg.attrtype_to = ROLE_MANAGED_ATTR_NAME; ++ ++ slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &clientFilter); ++ rc = slapi_filter_apply(clientFilter, role_substitute_type, &arg, &error_code); ++ if (rc == SLAPI_FILTER_SCAN_NOMORE) { ++ return SEARCH_REWRITE_CALLBACK_CONTINUE; /* Let's others rewriter play */ ++ } else { ++ slapi_log_err(SLAPI_LOG_ERR, ++ "example_foo2cn_filter_rewriter", "Could not update the search filter - error %d (%d)\n", ++ rc, error_code); ++ return SEARCH_REWRITE_CALLBACK_ERROR; /* operation error */ ++ } ++} +diff --git a/ldap/servers/slapd/back-ldbm/ldbm_attr.c b/ldap/servers/slapd/back-ldbm/ldbm_attr.c +index 1d86c2db4..07f3058a3 100644 +--- a/ldap/servers/slapd/back-ldbm/ldbm_attr.c ++++ b/ldap/servers/slapd/back-ldbm/ldbm_attr.c +@@ -1039,33 +1039,6 @@ ldbm_compute_evaluator(computed_attr_context *c, char *type, Slapi_Entry *e, sla + + */ + +-/* Before calling this function, you must free all the parts +- which will be overwritten, this function dosn't know +- how to do that */ +-static int +-replace_filter(Slapi_Filter *f, char *s) +-{ +- Slapi_Filter *newf = NULL; +- Slapi_Filter *temp = NULL; +- char *buf = slapi_ch_strdup(s); +- +- newf = slapi_str2filter(buf); +- slapi_ch_free((void **)&buf); +- +- if (NULL == newf) { +- return -1; +- } +- +- /* Now take the parts of newf and put them in f */ +- /* An easy way to do this is to preserve the "next" ptr */ +- temp = f->f_next; +- *f = *newf; +- f->f_next = temp; +- /* Free the new filter husk */ +- slapi_ch_free((void **)&newf); +- return 0; +-} +- + static void + find_our_friends(char *s, int *has, int *num) + { +@@ -1075,30 +1048,6 @@ find_our_friends(char *s, int *has, int *num) + } + } + +-/* Free the parts of a filter we're about to overwrite */ +-void +-free_the_filter_bits(Slapi_Filter *f) +-{ +- /* We need to free: */ +- switch (f->f_choice) { +- case LDAP_FILTER_EQUALITY: +- case LDAP_FILTER_GE: +- case LDAP_FILTER_LE: +- case LDAP_FILTER_APPROX: +- ava_done(&f->f_ava); +- break; +- +- case LDAP_FILTER_PRESENT: +- if (f->f_type != NULL) { +- slapi_ch_free((void **)&(f->f_type)); +- } +- break; +- +- default: +- break; +- } +-} +- + static int + grok_and_rewrite_filter(Slapi_Filter *f) + { +@@ -1116,11 +1065,9 @@ grok_and_rewrite_filter(Slapi_Filter *f) + rhs = f->f_ava.ava_value.bv_val; + if (has) { + if (0 == strcasecmp(rhs, "TRUE")) { +- free_the_filter_bits(f); +- replace_filter(f, "(&(numsubordinates=*)(numsubordinates>=1))"); ++ slapi_filter_replace_strfilter(f, "(&(numsubordinates=*)(numsubordinates>=1))"); + } else if (0 == strcasecmp(rhs, "FALSE")) { +- free_the_filter_bits(f); +- replace_filter(f, "(&(objectclass=*)(!(numsubordinates=*)))"); ++ slapi_filter_replace_strfilter(f, "(&(objectclass=*)(!(numsubordinates=*)))"); + } else { + return 1; /* Filter we can't rewrite */ + } +@@ -1133,7 +1080,7 @@ grok_and_rewrite_filter(Slapi_Filter *f) + + char *theType = f->f_ava.ava_type; + rhs_berval = f->f_ava.ava_value; +- replace_filter(f, "(&(numsubordinates=*)(numsubordinates=x))"); ++ slapi_filter_replace_ex(f, "(&(numsubordinates=*)(numsubordinates=x))"); + /* Now fixup the resulting filter so that x = rhs */ + slapi_ch_free((void **)&(f->f_and->f_next->f_ava.ava_value.bv_val)); + /*free type also */ +@@ -1143,8 +1090,7 @@ grok_and_rewrite_filter(Slapi_Filter *f) + } else { + if (rhs_number == 0) { + /* This is the same as hassubordinates=FALSE */ +- free_the_filter_bits(f); +- replace_filter(f, "(&(objectclass=*)(!(numsubordinates=*)))"); ++ slapi_filter_replace_strfilter(f, "(&(objectclass=*)(!(numsubordinates=*)))"); + } else { + return 1; + } +@@ -1166,14 +1112,13 @@ grok_and_rewrite_filter(Slapi_Filter *f) + rhs_num = atoi(rhs); + if (0 == rhs_num) { + /* If so, rewrite to same as numsubordinates=* */ +- free_the_filter_bits(f); +- replace_filter(f, "(objectclass=*)"); ++ slapi_filter_replace_strfilter(f, "(objectclass=*)"); + } else { + /* Rewrite to present and GE the rhs */ + char *theType = f->f_ava.ava_type; + rhs_berval = f->f_ava.ava_value; + +- replace_filter(f, "(&(numsubordinates=*)(numsubordinates>=x))"); ++ slapi_filter_replace_ex(f, "(&(numsubordinates=*)(numsubordinates>=x))"); + /* Now fixup the resulting filter so that x = rhs */ + slapi_ch_free((void **)&(f->f_and->f_next->f_ava.ava_value.bv_val)); + /*free type also */ +diff --git a/ldap/servers/slapd/filter.c b/ldap/servers/slapd/filter.c +index 1f0873c34..44b726a34 100644 +--- a/ldap/servers/slapd/filter.c ++++ b/ldap/servers/slapd/filter.c +@@ -1032,6 +1032,72 @@ slapi_filter_get_subfilt( + return (0); + } + ++/* ++ * Before calling this function, you must free all the parts ++ * which will be overwritten (i.e. slapi_free_the_filter_bits), ++ * this function dosn't know how to do that ++ */ ++int ++slapi_filter_replace_ex(Slapi_Filter *f, char *s) ++{ ++ Slapi_Filter *newf = NULL; ++ Slapi_Filter *temp = NULL; ++ char *buf = slapi_ch_strdup(s); ++ ++ newf = slapi_str2filter(buf); ++ slapi_ch_free((void **)&buf); ++ ++ if (NULL == newf) { ++ return -1; ++ } ++ ++ /* Now take the parts of newf and put them in f */ ++ /* An easy way to do this is to preserve the "next" ptr */ ++ temp = f->f_next; ++ *f = *newf; ++ f->f_next = temp; ++ /* Free the new filter husk */ ++ slapi_ch_free((void **)&newf); ++ return 0; ++} ++ ++/* ++ * Free the parts of a filter we're about to overwrite ++ * moved from ldbm_attr.c ++ */ ++void ++slapi_filter_free_bits(Slapi_Filter *f) ++{ ++ /* We need to free: */ ++ switch (f->f_choice) { ++ case LDAP_FILTER_EQUALITY: ++ case LDAP_FILTER_GE: ++ case LDAP_FILTER_LE: ++ case LDAP_FILTER_APPROX: ++ ava_done(&f->f_ava); ++ break; ++ ++ case LDAP_FILTER_PRESENT: ++ if (f->f_type != NULL) { ++ slapi_ch_free((void **)&(f->f_type)); ++ } ++ break; ++ ++ default: ++ break; ++ } ++} ++ ++/* ++ * it replaces the bits of the Slapi_Filter with the ones taken from strfilter ++ */ ++int ++slapi_filter_replace_strfilter(Slapi_Filter *f, char *strfilter) ++{ ++ slapi_filter_free_bits(f); ++ return (slapi_filter_replace_ex(f, strfilter)); ++} ++ + static void + filter_normalize_ava(struct slapi_filter *f, PRBool norm_values) + { +diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h +index e1f7beba5..79d04e418 100644 +--- a/ldap/servers/slapd/slapi-plugin.h ++++ b/ldap/servers/slapd/slapi-plugin.h +@@ -5240,6 +5240,9 @@ int slapi_filter_get_choice(Slapi_Filter *f); + int slapi_filter_get_ava(Slapi_Filter *f, char **type, struct berval **bval); + int slapi_filter_get_attribute_type(Slapi_Filter *f, char **type); + int slapi_filter_get_subfilt(Slapi_Filter *f, char **type, char **initial, char ***any, char ** final); ++int slapi_filter_replace_ex(Slapi_Filter *f, char *s); ++void slapi_filter_free_bits(Slapi_Filter *f); ++int slapi_filter_replace_strfilter(Slapi_Filter *f, char *strfilter); + Slapi_Filter *slapi_filter_list_first(Slapi_Filter *f); + Slapi_Filter *slapi_filter_list_next(Slapi_Filter *f, Slapi_Filter *fprev); + Slapi_Filter *slapi_str2filter(char *str); +-- +2.41.0 + diff --git a/0002-Issue-5156-RFE-that-implement-slapi_memberof-5694.patch b/0002-Issue-5156-RFE-that-implement-slapi_memberof-5694.patch new file mode 100644 index 0000000..4630436 --- /dev/null +++ b/0002-Issue-5156-RFE-that-implement-slapi_memberof-5694.patch @@ -0,0 +1,6384 @@ +From b7dccdc0b3b864c354416bf6a1d0040e0a586e97 Mon Sep 17 00:00:00 2001 +From: tbordaz +Date: Fri, 21 Apr 2023 11:25:29 +0200 +Subject: [PATCH 2/5] Issue 5156 - RFE that implement slapi_memberof (#5694) + +Bug description: + The RFE #5156 implements a new slapi function slapi_memberof. + This function is described in + https://www.port389.org/docs/389ds/design/slapi_memberof.html + For a given target entry, it allows the caller to retrieve + all entries that have a membership relation to that entry. + Typically, retrieving the groups that the given entry + is memberof. + This PR contains the implementation of slapi_memberof. + + This PR contains part of the tests of slapi_memberof. + It does not contain the tests that are based on + already computed 'memberof' attribute (memberof plugin). + Those remaining tests will be reviewed later + + This PR contains the remaining tests of slapi_memberof that + verify the ability of slapi_memberof to reuse 'memberof' + values. (MEMBEROF_REUSE_ONLY, MEMBEROF_REUSE_IF_POSSIBLE) + + This PR also fixes some bugs in the slapi_membeof function + and the test plugin. + +Fix description: + This PR contains the implementation of slapi_memberof. + + The slapi_memberof function is called by the server or + plugins. + The tests implements a new extop plugins 2.3.4.5.113730.6.7.1 + (test_slapi_memberof.c). + At startup the init function (test_slapi_memberof_init) of + the extop plugin read the plugin configuration entry. The + config entry contains params with which slapi_memberof + is called (scope, excludeScope, recurse, membership attr,...) + The extop receives a target entry as parameters and call + slapi_memberof with this target entry and the config params. + +relates: #5156 + +Reviewed by: Pierre Rogier, Mark Reynolds, Simon Pichugin, William Brown (Very big thanks!!!) +--- + Makefile.am | 1 + + .../slapi_memberof/basic_interface_test.py | 4423 +++++++++++++++++ + ldap/servers/slapd/main.c | 2 + + ldap/servers/slapd/slapi-memberof.c | 1306 +++++ + ldap/servers/slapd/slapi-plugin.h | 35 + + ldap/servers/slapd/slapi-private.h | 5 + + .../slapd/test-plugins/test_slapi_memberof.c | 476 ++ + 7 files changed, 6248 insertions(+) + create mode 100644 dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py + create mode 100644 ldap/servers/slapd/slapi-memberof.c + create mode 100644 ldap/servers/slapd/test-plugins/test_slapi_memberof.c + +diff --git a/Makefile.am b/Makefile.am +index a6ce565f6..0dd546356 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -1142,6 +1142,7 @@ libslapd_la_SOURCES = ldap/servers/slapd/add.c \ + ldap/servers/slapd/security_wrappers.c \ + ldap/servers/slapd/slapd_plhash.c \ + ldap/servers/slapd/slapi_counter.c \ ++ ldap/servers/slapd/slapi-memberof.c \ + ldap/servers/slapd/slapi2runtime.c \ + ldap/servers/slapd/snmp_collator.c \ + ldap/servers/slapd/sort.c \ +diff --git a/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py b/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py +new file mode 100644 +index 000000000..c5ecf5227 +--- /dev/null ++++ b/dirsrvtests/tests/suites/slapi_memberof/basic_interface_test.py +@@ -0,0 +1,4423 @@ ++# --- 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 ---- ++ ++import pytest, os ++ ++import logging ++import ldap ++from lib389.backend import Backends, Backend ++from lib389.mappingTree import MappingTrees ++from lib389.configurations.sample import create_base_domain ++from ldap.extop import ExtendedRequest ++from pyasn1.type import namedtype, univ ++from pyasn1.codec.ber import encoder, decoder ++from lib389.utils import ensure_bytes, get_plugin_dir ++from ldap.extop import ExtendedRequest, ExtendedResponse ++from pyasn1.type import namedtype, univ ++from pyasn1.codec.ber import encoder, decoder ++from lib389 import Entry ++ ++from lib389._constants import DEFAULT_SUFFIX, PW_DM ++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 ++ ++pytestmark = pytest.mark.tier0 ++log = logging.getLogger(__name__) ++ ++ ++class SlapiMemberofRequestValue(univ.Sequence): ++ pass ++ ++class SlapiMemberofRequest(ExtendedRequest): ++ def __init__(self, requestValidLifeTime=0): ++ self.requestName = '2.3.4.5.113730.6.7.1' ++ ++ def encodedRequestValue(self): ++ v = SlapiMemberofRequestValue() ++ return encoder.encode(v) ++ ++@pytest.fixture(scope="module") ++def install_test_plugin(topo): ++ import subprocess ++ import shutil ++ import os ++ import sys ++ import re ++ import pdb ++ import random ++ ++ current_dir = os.getcwd() ++ # create a build directory ++ build_dir="/tmp/build.%d" % (random.randint(1, 10000)) ++ os.makedirs(build_dir, exist_ok=True) ++ cmd_str="chmod 755 %s" % build_dir ++ subprocess.run(cmd_str, shell=True) ++ os.chdir(build_dir) ++ ++ # Retrieve the path of the workspace (from the path of the test) ++ workspace = None ++ for i in range(0, len(sys.argv)): ++ if sys.argv[i].find("slapi_memberof") > -1: ++ log.info("Workspace is: %s" % sys.argv[i]) ++ workspace=re.sub("dirsrvtest.*$", "", sys.argv[i]) ++ else: ++ log.info("Workspace is not: %s" % sys.argv[i]) ++ ++ if not workspace: ++ log.info("Fail to Retrieve from the repos containing slapi_memberof test plugin source") ++ log.info("using the current directory as workspace") ++ workspace=current_dir ++ ++ # Gather the include files from 'ldap/include' and 'ldap/servers/slapd' ++ for the_include in ["portable.h", "avl.h", "ldaprot.h"]: ++ include_file="%s/ldap/include/%s" % (workspace, the_include) ++ log.info("Retrieved from the repos: %s" % include_file) ++ file_path="%s/%s" % (build_dir, the_include) ++ shutil.copy(include_file, file_path) ++ cmd_str="chmod 666 %s" % file_path ++ subprocess.run(cmd_str, shell=True) ++ ++ for the_include in ["slap.h", "slapi-private.h", "slapi-plugin.h", "haproxy.h",\ ++ "slapi_pal.h", "csngen.h", "uuid.h", "disconnect_errors.h",\ ++ "pw.h", "filter.h", "proto-slap.h", "intrinsics.h", "slapi-plugin-compat4.h"]: ++ include_file="%s/ldap/servers/slapd/%s" % (workspace, the_include) ++ log.info("Retrieve from the repos: %s" % include_file) ++ file_path="%s/%s" % (build_dir, the_include) ++ shutil.copy(include_file, file_path) ++ cmd_str="chmod 666 %s" % file_path ++ subprocess.run(cmd_str, shell=True) ++ ++ # retrieve the test plugin source ++ log.info("use the default location") ++ src_file="%s/ldap/servers/slapd/test-plugins/test_slapi_memberof.c" % (workspace) ++ dst_file="%s/%s" % (build_dir, "test_slapi_memberof.c") ++ log.info("Retrieve from the repos: %s" % src_file) ++ shutil.copy(src_file, dst_file) ++ cmd_str="chmod 666 %s" % dst_file ++ subprocess.run(cmd_str, shell=True) ++ test_plugin_location=dst_file ++ ++ # ++ # If needed (if PR not pushed yet) to craft slapi-plugin.h ++ # ++ file_path_old = "%s/slapi-plugin.h" % (build_dir) ++ file_path_new = "%s/slapi-plugin.h.new" % (build_dir) ++ slapi_plugin_old = open(file_path_old) ++ ++ # before crafting check if slapi_memberof defs are present ++ need_to_craft = True ++ for line in slapi_plugin_old: ++ if "Slapi_MemberOfConfig" in line: ++ need_to_craft = False ++ break ++ ++ if need_to_craft: ++ log.info("Need to craft slapi-plugin.h") ++ slapi_plugin_old.seek(0, 0) ++ slapi_plugin_new = open(file_path_new, "w") ++ ++ # definitions that were missing, add them ++ struct_slapi_memberof = """ ++ ++#include ++typedef enum { ++ MEMBEROF_REUSE_ONLY, ++ MEMBEROF_REUSE_IF_POSSIBLE, ++ MEMBEROF_RECOMPUTE ++} memberof_flag_t; ++ ++typedef struct _slapi_memberofresult { ++ Slapi_ValueSet *nsuniqueid_vals; ++ Slapi_ValueSet *dn_vals; ++ PRBool maxgroups_reached; /* flag is true if the number of groups hit the max limit */ ++} Slapi_MemberOfResult; ++ ++typedef struct _slapi_memberofconfig ++{ ++ char **groupattrs; ++ PRBool subtree_search; ++ int allBackends; ++ Slapi_DN **entryScopes; ++ Slapi_DN **entryScopeExcludeSubtrees; ++ PRBool recurse; ++ int maxgroups; ++ memberof_flag_t flag; ++ char *error_msg; ++ int errot_msg_lenght; ++ int entryScopeCount; /* private to slapi_memberof */ ++ int entryExcludeScopeCount; /* private to slapi_memberof */ ++ PRBool maxgroups_reached; /* private to slapi_memberof */ ++ const char *memberof_attr; /* private to slapi_memberof */ ++ Slapi_Attr *dn_syntax_attr; /* private to slapi_memberof */ ++ PLHashTable *ancestors_cache; /* private to slapi_memberof */ ++ int current_maxgroup; /* private to slapi_memberof */ ++} Slapi_MemberOfConfig; ++ ++""" ++ for line in slapi_plugin_old: ++ if re.search(r"^#endif.*SLAPIPLUGIN_H_.*$", line): ++ slapi_plugin_new.write(struct_slapi_memberof) ++ slapi_plugin_new.write("\n") ++ slapi_plugin_new.write(line) ++ ++ slapi_plugin_old.close() ++ slapi_plugin_new.close() ++ os.remove(file_path_old) ++ shutil.move(file_path_new, file_path_old) ++ ++ # ++ # If needed (if PR not pushed yet) to craft slapi-private.h ++ # ++ file_path_old = "%s/slapi-private.h" % (build_dir) ++ file_path_new = "%s/slapi-private.h.new" % (build_dir) ++ slapi_private_old = open(file_path_old) ++ ++ # before crafting check if slapi_memberof defs are present ++ need_to_craft = True ++ for line in slapi_private_old: ++ if "slapi_memberof" in line: ++ need_to_craft = False ++ break ++ ++ if need_to_craft: ++ log.info("Need to craft slapi-private.h") ++ slapi_private_old.seek(0, 0) ++ slapi_private_new = open(file_path_new, "w") ++ ++ # definitions that were missing, add them ++ struct_slapi_memberof = """ ++int slapi_memberof(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_MemberOfResult *result); ++void slapi_memberof_free_memberof_plugin_config(); ++int slapi_memberof_load_memberof_plugin_config(); ++ ++""" ++ ++ for line in slapi_private_old: ++ if re.search(r"^void dup_ldif_line.*$", line): ++ slapi_private_new.write(struct_slapi_memberof) ++ slapi_private_new.write("\n") ++ slapi_private_new.write(line) ++ ++ slapi_private_old.close() ++ slapi_private_new.close() ++ os.remove(file_path_old) ++ shutil.move(file_path_new, file_path_old) ++ ++ # build the plugin into a shared library ++ test_plugin_object="%s/test_slapi_memberof.o" % build_dir ++ test_plugin_sharedlib="%s/libtest_slapi_memberof-plugin.so" % build_dir ++ cmd_str="/usr/bin/gcc -I./ldap/include -I./ldap/servers/slapd -I./include -I. -I/usr/include -I/usr/include/nss3 -I%s -I/usr/include/nspr4 -g -O2 -Wall -c %s -fPIC -DPIC -o %s" % (build_dir, test_plugin_location, test_plugin_object) ++ subprocess.run(cmd_str, shell=True) ++ cmd_str="/usr/bin/gcc -shared -fPIC -DPIC %s -Wl,-rpath -Wl,/usr/lib64/dirsrv -L/usr/lib64/dirsrv/ /usr/lib64/dirsrv/libslapd.so.0 -lldap -llber -lc -Wl,-z,now -g -O2 -O2 -m64 -Wl,-z -Wl,relro -Wl,--as-needed -Wl,-z -Wl,now -Wl,-soname -Wl,libtest_slapi_memberof-plugin.so -o %s" % (test_plugin_object, test_plugin_sharedlib) ++ subprocess.run(cmd_str, shell=True) ++ ++ # install the test plugin ++ cmd_str="chmod 755 %s" % test_plugin_sharedlib ++ subprocess.run(cmd_str, shell=True) ++ shutil.copy(test_plugin_sharedlib, topo.standalone.get_plugin_dir()) ++ ++ ++def _check_res_vs_expected(msg, res, expected): ++ log.info("Checking %s expecting %d entries" % (msg, len(expected))) ++ assert len(expected) == len(res) ++ expected_str_lower = [] ++ for i in expected: ++ expected_str_lower.append(str(i).lower()) ++ ++ res_str_lower = [] ++ for i in res: ++ res_str_lower.append(str(i).lower()) ++ ++ for i in expected_str_lower: ++ log.info("Check that %s is present" % (i)) ++ assert i in res_str_lower ++ ++EMPTY_RESULT="no error msg" ++ ++def _extop_test_slapi_member(server, dn, relation): ++ value = univ.OctetString(dn) ++ value_encoded = encoder.encode(value) ++ ++ extop = ExtendedRequest(requestName = '2.3.4.5.113730.6.7.1', requestValue=value_encoded) ++ (oid_response, res) = server.extop_s(extop) ++ d1, d2 = decoder.decode(res) ++ log.info("The entries refering to %s as %s are:" % (dn, relation)) ++ for i in d1: ++ log.info(" - %s" % i) ++ return d1 ++ ++ ++def replace_manager(server, dn, managers): ++ mod = [(ldap.MOD_REPLACE, 'manager', managers)] ++ server.modify_s(dn, mod) ++ ++def add_entry(server, uid, manager=None, subtree=None): ++ if (subtree): ++ dn = 'uid=%s,ou=%s,ou=People,%s' % (uid, subtree, DEFAULT_SUFFIX) ++ else: ++ dn = 'uid=%s,ou=People,%s' % (uid, DEFAULT_SUFFIX) ++ server.add_s(Entry((dn, {'objectclass': 'top person extensibleObject'.split(), ++ 'uid': uid, ++ 'cn': uid, ++ 'sn': uid}))) ++ if manager: ++ replace_manager(server, dn, manager) ++ return dn ++ ++def test_slapi_memberof_simple(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_member ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 4c2595eb-a947-4c0b-996c-e499db67d11a ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entry ++ 2. configure test_slapi_memberof as described above ++ 3. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_allbackends_on(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_member ++ It exists several backends and manager relationship cross those backends ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' <---- ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 910c43a0-04ae-48f1-9e3c-6d97ba5bcb71 ++ :setup: Standalone instance ++ :steps: ++ 1. create a second backend with foo_bar entry ++ 2. provision a set of entries in default backend with foo_bar being ++ manager of entry e_1_parent_1_1_1_3_0 that is in default backend ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ slapi_memberof(foo_bar, "manager") -> e_1_parent_1_1_1_3_0 ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ # create a second backend ++ second_suffix='dc=foo,dc=bar' ++ be_name='fooBar' ++ be1 = Backend(topo.standalone) ++ be1.create(properties={ ++ 'cn': be_name, ++ 'nsslapd-suffix': second_suffix, ++ }, ++ ) ++ # Create the domain entry ++ create_base_domain(topo.standalone, second_suffix) ++ rdn='foo_bar' ++ dn_entry_foo_bar='uid=%s,%s' % (rdn, second_suffix) ++ topo.standalone.add_s(Entry((dn_entry_foo_bar, {'objectclass': 'top person extensibleObject'.split(), ++ 'uid': rdn, ++ 'cn': rdn, ++ 'sn': rdn}))) ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ # make foo_bar entry manager of e_1_parent_1_1_1_3_0 ++ replace_manager(topo.standalone, e_1_parent_1_1_1_3_0, [ensure_bytes(dn_entry_foo_bar)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': [DEFAULT_SUFFIX, second_suffix], ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ # Check dn_entry_foo_bar ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=dn_entry_foo_bar, relation="manager") ++ _check_res_vs_expected("organisation reporting to dn_entry_foo_bar", res, expected) ++ ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ topo.standalone.delete_s(dn_entry_foo_bar) ++ be1.delete() ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_allbackends_off(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_member ++ It exists several backends and manager relationship cross those backends ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'off' <---- ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 56fb0c16-8086-429b-adf0-fff0eb8e121e ++ :setup: Standalone instance ++ :steps: ++ 1. create a second backend with foo_bar entry ++ 2. provision a set of entries in default backend with foo_bar being ++ manager of entry e_1_parent_1_1_1_3_0 that is in default backend ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ slapi_memberof(foo_bar, "manager") NOT -> e_1_parent_1_1_1_3_0 ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ # Create second backend ++ second_suffix='dc=foo,dc=bar' ++ be_name='fooBar' ++ be1 = Backend(topo.standalone) ++ be1.create(properties={ ++ 'cn': be_name, ++ 'nsslapd-suffix': second_suffix, ++ }, ++ ) ++ # Create the domain entry ++ create_base_domain(topo.standalone, second_suffix) ++ rdn='foo_bar' ++ dn_entry_foo_bar='uid=%s,%s' % (rdn, second_suffix) ++ topo.standalone.add_s(Entry((dn_entry_foo_bar, {'objectclass': 'top person extensibleObject'.split(), ++ 'uid': rdn, ++ 'cn': rdn, ++ 'sn': rdn}))) ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ # make foo_bar entry manager of e_1_parent_1_1_1_3_0 ++ replace_manager(topo.standalone, e_1_parent_1_1_1_3_0, [ensure_bytes(dn_entry_foo_bar)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': [DEFAULT_SUFFIX, second_suffix], ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ # Check dn_entry_foo_bar is not manager of e_1_parent_1_1_1_3_0 because slapimemberOfAllBackends=off ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=dn_entry_foo_bar, relation="manager") ++ _check_res_vs_expected("organisation reporting to dn_entry_foo_bar", res, expected) ++ ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ topo.standalone.delete_s(dn_entry_foo_bar) ++ be1.delete() ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_memberattr(topo, request, install_test_plugin): ++ """ ++ Test that membership hierarchy (member) is computed with slapi_member ++ the membership is done with 'manager' attribute but slapi_memberof ++ called with 'member' attribute. As there is no 'member' then ++ membership returns empty_results ++ with following parameters ++ - membership attribute: 'member' <---- ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 373f7f65-185f-4b06-a0a5-3e23692b87f1 ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entries in default backend ++ with membership using 'manager' ++ 2. configure test_slapi_memberof as described above ++ so checking membership using 'member' ++ 3. check computed membership vs expected result ++ all empty_result because no entry has 'member' ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'member', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_scope(topo, request, install_test_plugin): ++ """ ++ Test that membership hierarchy (member) is computed with slapi_member ++ Only entries in the subtree scope (e_2_parent_1_0) gets valid ++ computation of the membership ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: ou=subtree,ou=People,dc=example,dc=com <---- ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 6c7587e0-0bc4-4847-b403-773d7314aa31 ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entries in default backend ++ 2. configure test_slapi_memberof as described above ++ so only entries under e_2_parent_1_0 are taken into ++ consideration ++ 3. check computed membership vs expected result ++ Only entries under e_2_parent_1_0 get no empty results ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 (subtree) <---- ++ -- e_1_parent_2_1_0 (subtree) <---- ++ -- e_2_parent_2_1_0 (subtree) <---- ++ --- e_1_parent_2_2_1_0 (subtree) <---- ++ -- e_3_parent_2_1_0 (subtree) <---- ++ -- e_4_parent_2_1_0 (subtree) <---- ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ ++ subtree="subtree" ++ dn_subtree = 'ou=%s,ou=People,%s' % (subtree, DEFAULT_SUFFIX) ++ topo.standalone.add_s(Entry((dn_subtree, {'objectclass': 'top organizationalunit'.split(), ++ 'ou': subtree}))) ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)], subtree=subtree) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)], subtree=subtree) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': dn_subtree, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # Check e_1_parent_2_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_2_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [ e_1_parent_2_2_1_0 ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ topo.standalone.delete_s(dn_subtree) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_excludescope(topo, request, install_test_plugin): ++ """ ++ Test that membership hierarchy (member) is computed with slapi_member ++ Entries in the subtree excludeescope (e_2_parent_1_0) are ignored ++ computation of the membership ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: ou=subtree,ou=People,dc=example,dc=com <---- ++ - Maximum return entries: None ++ ++ :id: bdb17e7e-289c-4b56-83d5-0eb54d0c660e ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entries in default backend ++ 2. configure test_slapi_memberof as described above ++ so entries under e_2_parent_1_0 are ignored ++ 3. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 (subtree) <---- ++ -- e_1_parent_2_1_0 (subtree) <---- ++ -- e_2_parent_2_1_0 (subtree) <---- ++ --- e_1_parent_2_2_1_0 (subtree) <---- ++ -- e_3_parent_2_1_0 (subtree) <---- ++ -- e_4_parent_2_1_0 (subtree) <---- ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ ++ subtree="subtree" ++ dn_subtree = 'ou=%s,ou=People,%s' % (subtree, DEFAULT_SUFFIX) ++ topo.standalone.add_s(Entry((dn_subtree, {'objectclass': 'top organizationalunit'.split(), ++ 'ou': subtree}))) ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)], subtree=subtree) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)], subtree=subtree) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)], subtree=subtree) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScopeExcludeSubtree': dn_subtree, ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # Check e_1_parent_2_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_2_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [ EMPTY_RESULT ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [ e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0 ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [ e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0 ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [ e_1_parent_1_1_1_3_0 ] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ topo.standalone.delete_s(dn_subtree) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_skip_nested(topo, request, install_test_plugin): ++ """ ++ When searching the management (manager) hierarchy it stops at the first level ++ no recursion ++ Test that management hierarchy is computed with slapi_member ++ It is done stopping at the first level, so the direct subordinate ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'on' <---- ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: ou=subtree,ou=People,dc=example,dc=com ++ - Maximum return entries: None ++ ++ :id: c9b5617f-9058-40f5-bdd6-a560bc67b30d ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entries in default backend ++ 2. configure test_slapi_memberof as described above ++ 3. check computed membership vs expected result ++ only direct subordinate are returned ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ ++ subtree="subtree" ++ dn_subtree = 'ou=%s,ou=People,%s' % (subtree, DEFAULT_SUFFIX) ++ topo.standalone.add_s(Entry((dn_subtree, {'objectclass': 'top organizationalunit'.split(), ++ 'ou': subtree}))) ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'on', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_2_parent_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ topo.standalone.delete_s(dn_subtree) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_maxgroup(topo, request, install_test_plugin): ++ """ ++ When searching the management (manager) hierarchy it stops when ++ a maximum subordinates are retrieved ++ Test that management hierarchy is computed with slapi_member ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' <---- ++ - computation mode: recompute ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: ou=subtree,ou=People,dc=example,dc=com ++ - Maximum return entries: 3 <-- ++ ++ :id: 83a4c668-99d0-4f47-ac89-a7f7fc620340 ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entries in default backend ++ 2. configure test_slapi_memberof as described above ++ 3. check computed membership vs expected result ++ only direct subordinate are returned ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '3', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_1(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. As memberof plugin is not enabled, it falls back ++ to regular computation (recompute) ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 8f75e4c9-60d4-41b8-8b25-df9fe4b0231d ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entry ++ 2. configure test_slapi_memberof as described above ++ 3. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'on', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_2(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'membership attribute' ++ it falls back to regular computation (recompute) ++ with following parameters ++ - membership attribute: 'manager' <-- ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 2175578b-7f12-4f36-a4fe-eb401422643d ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'uniquemember' memberOfGroupAttr <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'uniquemember') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_3(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'memberOfAllBackends attribute' ++ it falls back to regular computation (recompute) ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'off' <-- ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 11615fc6-67e8-4c4a-be76-d57baf0e1706 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'memberOfAllBackends: on' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'on') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_reuse_if_possible_4(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'memberOfSkipNested' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' <-- ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 305c99ba-5835-4b8c-bfb7-11deeea5eedc ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'memberOfSkipNested: on' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfSkipNested', 'on') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_reuse_if_possible_5(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'memberOfAttr' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof <-- ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 66d2ed29-5d14-487a-b28a-5660962c7c6c ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'memberOfAttr: member' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ ++ # For the test memberOfAttr should differ from 'memberof' that is ++ # used in slapi_memberof call. We can not use a dummy attribute ++ # because it requires to be a DN syntax. Let's use 'member' ++ memberof.replace('memberOfAttr', 'member') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_6(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScope' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: ou=people,dc=example,dc=com <-- ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 4fbefa39-6c06-47c4-8818-a102944b7f29 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof without 'memberOfEntryScope' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': "ou=People,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_reuse_if_possible_7(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScope' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: ou=people,dc=example,dc=com <-- ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: b8cfca23-742f-44f0-8bb7-f93371954d40 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'memberOfEntryScope: ou=groups,dc=example,dc=com' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', 'ou=groups,dc=example,dc=com') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': "ou=People,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_8(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScope' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: ou=people,dc=example,dc=com <-- ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 5c990df5-8aa6-44c6-a9e1-f161c3e01d1e ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with ++ - 'memberOfEntryScope: ou=groups,dc=example,dc=com' <-- ++ - 'memberOfEntryScope: ou=people,dc=example,dc=com' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) ++ memberof.replace('memberOfEntryScope', 'ou=groups,dc=example,dc=com') ++ memberof.add('memberOfEntryScope', 'ou=people,dc=example,dc=com') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': "ou=People,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_9(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScopeExcludeSubtree' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: None ++ - ExcludeScope: ou=groups,dc=example,dc=com <-- ++ - Maximum return entries: None ++ ++ :id: 55ffe094-482b-4e2f-9d34-8b0a1ebd5248 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof without 'memberOfEntryScopeExcludeSubtree' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfEntryScopeExcludeSubtree': "ou=Groups,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++ ++def test_slapi_memberof_reuse_if_possible_10(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScopeExcludeSubtree' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: ou=foo,dc=example,dc=com <-- ++ - Maximum return entries: None ++ ++ :id: 6c26619d-e0e2-4b3e-938b-4cab817e928f ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with 'memberOfEntryScopeExcludeSubtree: ou=groups,dc=example,dc=com' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) ++ memberof.replace('memberOfEntryScopeExcludeSubtree', 'ou=groups,dc=example,dc=com') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfEntryScopeExcludeSubtree': "ou=Foo,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_if_possible_11(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to reuse IF POSSIBLE the computed values ++ from memberof plugins. ++ Memberof plugin is enabled, but with a different 'slapimemberOfEntryScope' attr ++ it falls back to regular computation (recompute) ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: None ++ - ExcludeScope: ou=foo1,dc=example,dc=com <-- ++ - Maximum return entries: None ++ ++ :id: 4c15995e-21a9-4443-8027-a0908345be50 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with ++ - 'memberOfEntryScopeExcludeSubtree: ou=foo1,dc=example,dc=com' <-- ++ - 'memberOfEntryScopeExcludeSubtree: ou=foo2,dc=example,dc=com' <-- ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) ++ memberof.replace('memberOfEntryScopeExcludeSubtree', 'ou=foo1,dc=example,dc=com') ++ memberof.add('memberOfEntryScopeExcludeSubtree', 'ou=foo2,dc=example,dc=com') ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_IF_POSSIBLE', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfEntryScopeExcludeSubtree': "ou=foo1,%s" % DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_only_1(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to ONLY reuse the computed values ++ from memberof plugins. As memberof plugin is not enabled, it returns ++ no memberof. ++ with following parameters ++ - membership attribute: 'manager' ++ - span over all backends: 'on' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_ONLY <-- ++ - Scope: DEFAULT_SUFFIX ++ - ExcludeScope: None ++ - Maximum return entries: None ++ ++ :id: 7be9b188-2e84-4454-b6db-9e176014582a ++ :setup: Standalone instance ++ :steps: ++ 1. provision a set of entry ++ 2. configure test_slapi_memberof as described above ++ 3. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_ONLY', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++def test_slapi_memberof_reuse_only_2(topo, request, install_test_plugin): ++ """ ++ Test that management hierarchy (manager) is computed with slapi_memberof ++ It requires slapi_memberof to ONLY reuse the computed values ++ from memberof plugins. As memberof plugin is enabled, it returns ++ memberof. ++ with following parameters ++ - member attribute: memberof ++ - membership attribute: 'manager' ++ - span over all backends: 'off' ++ - skip nesting membership: 'off' ++ - computation mode: MEMBEROF_REUSE_IF_POSSIBLE <-- ++ - Scope: None ++ - ExcludeScope: ou=foo1,dc=example,dc=com <-- ++ - Maximum return entries: None ++ ++ :id: fb4f8c86-aa39-4252-90e0-36cfd7b3dd80 ++ :setup: Standalone instance ++ :steps: ++ 1. Configure memberof with ++ 2. provision a set of entry ++ 3. configure test_slapi_memberof as described above ++ 4. check computed membership vs expected result ++ :expectedresults: ++ 1. Operation should succeed ++ 2. Operation should succeed ++ 3. Operation should succeed ++ 4. Operation should succeed ++ ++ DIT is : ++ e_1_parent_0 ++ - e_1_parent_1_0 ++ -- e_1_parent_1_1_0 ++ --- e_1_parent_1_1_1_0 ++ --- e_2_parent_1_1_1_0 ++ --- e_3_parent_1_1_1_0 ++ --- e_4_parent_1_1_1_0 ++ --- e_5_parent_1_1_1_0 ++ -- e_2_parent_1_1_0 ++ - e_2_parent_1_0 ++ -- e_1_parent_2_1_0 ++ -- e_2_parent_2_1_0 ++ --- e_1_parent_2_2_1_0 ++ -- e_3_parent_2_1_0 ++ -- e_4_parent_2_1_0 ++ e_2_parent_0 ++ - e_1_parent_2_0 ++ - e_2_parent_2_0 ++ - e_3_parent_2_0 ++ - e_4_parent_2_0 ++ e_3_parent_0 ++ - e_1_parent_3_0 ++ -- e_1_parent_1_3_0 ++ --- e_1_parent_1_1_3_0 ++ ---- e_1_parent_1_1_1_3_0 ++ """ ++ memberof = MemberOfPlugin(topo.standalone) ++ memberof.enable() ++ memberof.replace('memberOfAttr', 'memberof') ++ memberof.replace('memberOfGroupAttr', 'manager') ++ memberof.replace('memberOfAllBackends', 'off') ++ memberof.replace('memberOfSkipNested', 'off') ++ memberof.replace('memberOfEntryScope', DEFAULT_SUFFIX) ++ topo.standalone.restart() ++ ++ user = UserAccounts(topo.standalone, DEFAULT_SUFFIX) ++ ++ # First subtree ++ e_1_parent_0 = add_entry(topo.standalone, uid="e_1_parent_0") ++ ++ e_1_parent_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_1_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_2_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_3_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_3_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_4_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_4_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ e_5_parent_1_1_1_0 = add_entry(topo.standalone, uid="e_5_parent_1_1_1_0", manager=[ensure_bytes(e_1_parent_1_1_0)]) ++ ++ e_2_parent_1_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_1_0", manager=[ensure_bytes(e_1_parent_1_0)]) ++ ++ e_2_parent_1_0 = add_entry(topo.standalone, uid="e_2_parent_1_0", manager=[ensure_bytes(e_1_parent_0)]) ++ ++ e_1_parent_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_2_parent_2_1_0 = add_entry(topo.standalone, uid="e_2_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_1_parent_2_2_1_0 = add_entry(topo.standalone, uid="e_1_parent_2_2_1_0", manager=[ensure_bytes(e_2_parent_2_1_0)]) ++ e_3_parent_2_1_0 = add_entry(topo.standalone, uid="e_3_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ e_4_parent_2_1_0 = add_entry(topo.standalone, uid="e_4_parent_2_1_0", manager=[ensure_bytes(e_2_parent_1_0)]) ++ ++ # 2nd subtree ++ e_2_parent_0 = add_entry(topo.standalone, uid="e_2_parent_0") ++ ++ e_1_parent_2_0 = add_entry(topo.standalone, uid="e_1_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_2_parent_2_0 = add_entry(topo.standalone, uid="e_2_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_3_parent_2_0 = add_entry(topo.standalone, uid="e_3_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ e_4_parent_2_0 = add_entry(topo.standalone, uid="e_4_parent_2_0", manager=[ensure_bytes(e_2_parent_0)]) ++ ++ # third subtree ++ e_3_parent_0 = add_entry(topo.standalone, uid="e_3_parent_0") ++ ++ e_1_parent_3_0 = add_entry(topo.standalone, uid="e_1_parent_3_0", manager=[ensure_bytes(e_3_parent_0)]) ++ ++ e_1_parent_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_3_0", manager=[ensure_bytes(e_1_parent_3_0)]) ++ ++ e_1_parent_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_3_0)]) ++ ++ e_1_parent_1_1_1_3_0 = add_entry(topo.standalone, uid="e_1_parent_1_1_1_3_0", manager=[ensure_bytes(e_1_parent_1_1_3_0)]) ++ ++ dn_config = 'cn=test_slapi_memberof,cn=plugins,cn=config' ++ topo.standalone.add_s(Entry((dn_config, {'objectclass': 'top nsSlapdPlugin extensibleObject'.split(), ++ 'cn': 'test_slapi_memberof', ++ 'nsslapd-pluginPath': 'libtest_slapi_memberof-plugin', ++ 'nsslapd-pluginInitfunc': 'test_slapi_memberof_init', ++ 'nsslapd-pluginType': 'extendedop', ++ 'nsslapd-pluginEnabled': 'on', ++ 'nsslapd-plugin-depends-on-type': 'database', ++ 'nsslapd-pluginId': 'test_slapi_memberof-plugin', ++ 'slapimemberOfMemberDN': 'uid=test_user_11,ou=People,dc=example,dc=com', ++ 'slapimemberOfGroupAttr': 'manager', ++ 'slapimemberOfAttr': 'memberof', ++ 'slapimemberOfFlag': 'MEMBEROF_REUSE_ONLY', ++ 'slapimemberOfAllBackends': 'off', ++ 'slapimemberOfSkipNested': 'off', ++ 'slapimemberOfEntryScope': DEFAULT_SUFFIX, ++ 'slapimemberOfMaxGroup': '0', ++ 'nsslapd-pluginVersion': '2.3.2.202302131418git0e190fc3d', ++ 'nsslapd-pluginVendor': '389 Project', ++ 'nsslapd-pluginDescription': 'test_slapi_memberof extended operation plugin'}))) ++ topo.standalone.restart() ++ ++ # Check the first subtree ++ expected = [ e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_0, relation="manager") ++ _check_res_vs_expected("first subtree", res, expected) ++ ++ # Check the second subtree ++ expected = [e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_0, relation="manager") ++ _check_res_vs_expected("second subtree", res, expected) ++ ++ # Check the third subtree ++ expected = [e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_3_parent_0, relation="manager") ++ _check_res_vs_expected("third subtree", res, expected) ++ ++ # check e_1_parent_1_0 ++ expected = [e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_0", res, expected) ++ ++ # check e_1_parent_1_1_0 ++ expected = [e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_1_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_1_0", res, expected) ++ ++ # check e_2_parent_1_0 ++ expected = [e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_1_0", res, expected) ++ ++ # check e_2_parent_2_1_0 ++ expected = [e_1_parent_2_2_1_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_2_parent_2_1_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_2_parent_2_1_0", res, expected) ++ ++ # Check e_1_parent_3_0 ++ expected = [e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_3_0", res, expected) ++ ++ # Check e_1_parent_1_3_0 ++ expected = [e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_3_0 ++ expected = [e_1_parent_1_1_1_3_0] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_3_0", res, expected) ++ ++ # Check e_1_parent_1_1_1_3_0 ++ expected = [EMPTY_RESULT] ++ res = _extop_test_slapi_member(server=topo.standalone, dn=e_1_parent_1_1_1_3_0, relation="manager") ++ _check_res_vs_expected("organisation reporting to e_1_parent_1_1_1_3_0", res, expected) ++ ++ def fin(): ++ entries = [e_1_parent_0, e_1_parent_1_0, e_1_parent_1_1_0, e_1_parent_1_1_1_0, e_2_parent_1_1_1_0, e_3_parent_1_1_1_0, e_4_parent_1_1_1_0, e_5_parent_1_1_1_0, e_2_parent_1_1_0, e_2_parent_1_0, e_1_parent_2_1_0, e_2_parent_2_1_0, e_1_parent_2_2_1_0, e_3_parent_2_1_0, e_4_parent_2_1_0, e_2_parent_0, e_1_parent_2_0, e_2_parent_2_0, e_3_parent_2_0, e_4_parent_2_0, e_3_parent_0, e_1_parent_3_0, e_1_parent_1_3_0, e_1_parent_1_1_3_0, e_1_parent_1_1_1_3_0] ++ for entry in entries: ++ topo.standalone.delete_s(entry) ++ topo.standalone.delete_s(dn_config) ++ ++ request.addfinalizer(fin) ++ ++if __name__ == "__main__": ++ CURRENT_FILE = os.path.realpath(__file__) ++ pytest.main("-s -v %s" % CURRENT_FILE) ++ +diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c +index b8084faac..f24650547 100644 +--- a/ldap/servers/slapd/main.c ++++ b/ldap/servers/slapd/main.c +@@ -1064,6 +1064,7 @@ main(int argc, char **argv) + plugin_print_lists(); + plugin_startall(argc, argv, NULL /* specific plugin list */); + compute_plugins_started(); ++ slapi_memberof_load_memberof_plugin_config(); + (void) rewriters_init(); + if (housekeeping_start((time_t)0, NULL) == NULL) { + return_value = 1; +@@ -1132,6 +1133,7 @@ main(int argc, char **argv) + slapd_daemon(&ports_info); + } + slapi_log_err(SLAPI_LOG_INFO, "main", "slapd stopped.\n"); ++ slapi_memberof_free_memberof_plugin_config(); + reslimit_cleanup(); + vattr_cleanup(); + sasl_map_done(); +diff --git a/ldap/servers/slapd/slapi-memberof.c b/ldap/servers/slapd/slapi-memberof.c +new file mode 100644 +index 000000000..6f39965bb +--- /dev/null ++++ b/ldap/servers/slapd/slapi-memberof.c +@@ -0,0 +1,1306 @@ ++/** 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 ++ ++/* util.c -- utility functions -- functions available form libslapd */ ++#include ++#include ++#include ++#include ++#include ++#include "slap.h" ++#include ++#include ++ ++#define MEMBEROF_CACHE_DEBUG 0 ++#define MEMBEROF_CONFIG_DN "cn=MemberOf Plugin,cn=plugins,cn=config" ++#define MEMBEROF_GOUP_ATTR "memberofgroupattr" ++#define MEMBEROF_ATTR "memberofattr" ++#define MEMBEROF_INCLUDE_SCOPE "memberOfEntryScope" ++#define MEMBEROF_EXLCUDE_SCOPE "memberOfEntryScopeExcludeSubtree" ++#define MEMBEROF_ALL_BACKENDS "memberOfAllBackends" ++#define MEMBEROF_SKIP_NESTED "memberOfSkipNested" ++#define MEMBEROF_ENABLED "nsslapd-pluginEnabled" ++ ++struct memberof_plugin_config { ++ char **groupattrs; ++ char *memberof_attr; ++ PRBool all_backends; ++ PRBool skip_nested; ++ PRBool enabled; ++ char **include_scope; ++ char **exclude_scope; ++}; ++static struct memberof_plugin_config *memberof_config = NULL; ++ ++typedef struct _sm_memberof_get_groups_data { ++ Slapi_MemberOfConfig *config; ++ Slapi_Value *memberdn_val; ++ Slapi_ValueSet **groupvals; /* list of group DN which memberdn_val is member of */ ++ Slapi_ValueSet **group_norm_vals; ++ Slapi_ValueSet **nsuniqueidvals; /* equivalent to groupvals but with nsuniqueid */ ++ Slapi_ValueSet **already_seen_ndn_vals; ++ PRBool use_cache; ++} sm_memberof_get_groups_data; ++ ++ ++/* The key to access the hash table is the normalized DN ++ * The normalized DN is stored in the value because: ++ * - It is used in slapi_valueset_find ++ * - It is used to fill the memberof_get_groups_data.group_norm_vals ++ */ ++typedef struct _sm_memberof_cached_value { ++ char *key; ++ char *group_dn_val; ++ char *group_ndn_val; ++ char *nsuniqueid_val; ++ int valid; ++} sm_memberof_cached_value; ++ ++struct sm_cache_stat { ++ int total_lookup; ++ int successfull_lookup; ++ int total_add; ++ int total_remove; ++ int total_enumerate; ++ int cumul_duration_lookup; ++ int cumul_duration_add; ++ int cumul_duration_remove; ++ int cumul_duration_enumerate; ++}; ++static struct sm_cache_stat sm_cache_stat; ++ ++ ++ ++static sm_memberof_cached_value *sm_ancestors_cache_lookup(Slapi_MemberOfConfig *config, const char *ndn); ++static PLHashEntry *sm_ancestors_cache_add(Slapi_MemberOfConfig *config, const void *key, void *value); ++static void sm_ancestor_hashtable_entry_free(sm_memberof_cached_value *entry); ++static void sm_cache_ancestors(Slapi_MemberOfConfig *config, Slapi_Value **member_ndn_val, sm_memberof_get_groups_data *groups); ++static int sm_memberof_compare(Slapi_MemberOfConfig *config, const void *a, const void *b); ++static void sm_merge_ancestors(Slapi_Value **member_ndn_val, sm_memberof_get_groups_data *v1, sm_memberof_get_groups_data *v2); ++static int sm_memberof_entry_in_scope(Slapi_MemberOfConfig *config, Slapi_DN *sdn); ++static int sm_memberof_get_groups_callback(Slapi_Entry *e, void *callback_data); ++static void sm_report_error_msg(Slapi_MemberOfConfig *config, char* msg); ++static int sm_entry_get_groups(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, ++ Slapi_ValueSet *groupvals, Slapi_ValueSet *nsuniqueidvals); ++static PRBool sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool all_backends, ++ PRBool skip_nested, Slapi_DN **include_scope, Slapi_DN **exclude_scope, PRBool enabled_only); ++static void sm_add_ancestors_cbdata(sm_memberof_cached_value *ancestors, void *callback_data); ++static int sm_memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn, Slapi_MemberOfConfig *config, char **types, ++ plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache); ++static int sm_memberof_get_groups_r(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, sm_memberof_get_groups_data *data); ++static PRIntn sm_memberof_hash_compare_keys(const void *v1, const void *v2); ++static PRIntn sm_memberof_hash_compare_values(const void *v1, const void *v2); ++static PLHashNumber sm_memberof_hash_fn(const void *key); ++static PLHashTable *sm_hashtable_new(int usetxn); ++static PRIntn sm_ancestor_hashtable_remove(PLHashEntry *he, PRIntn index __attribute__((unused)), void *arg __attribute__((unused))); ++static void sm_ancestor_hashtable_empty(Slapi_MemberOfConfig *config, char *msg); ++ ++ ++#if MEMBEROF_CACHE_DEBUG ++static void ++sm_dump_cache_entry(sm_memberof_cached_value *double_check, const char *msg) ++{ ++ for (size_t i = 0; double_check[i].valid; i++) { ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_dump_cache_entry: %s -> %s/%s\n", ++ msg ? msg : "", ++ double_check[i].group_dn_val ? double_check[i].group_dn_val : "NULL", ++ double_check[i].nsuniqueid_val ? double_check[i].nsuniqueid_val : "NULL"); ++ } ++} ++#endif ++ ++static sm_memberof_cached_value * ++sm_ancestors_cache_lookup(Slapi_MemberOfConfig *config, const char *ndn) ++{ ++ sm_memberof_cached_value *e; ++#if defined(DEBUG) ++ long int start; ++ struct timespec tsnow; ++#endif ++ ++ sm_cache_stat.total_lookup++; ++ ++#if defined(DEBUG) ++ if (clock_gettime(CLOCK_REALTIME, &tsnow) != 0) { ++ start = 0; ++ } else { ++ start = tsnow.tv_nsec; ++ } ++#endif ++ ++ e = (sm_memberof_cached_value *) PL_HashTableLookupConst(config->ancestors_cache, (const void *) ndn); ++ ++#if defined(DEBUG) ++ if (start) { ++ if (clock_gettime(CLOCK_REALTIME, &tsnow) == 0) { ++ sm_cache_stat.cumul_duration_lookup += (tsnow.tv_nsec - start); ++ } ++ } ++#endif ++ ++ if (e) ++ sm_cache_stat.successfull_lookup++; ++ return e; ++} ++ ++/* 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 ++ */ ++ ++static void ++sm_ancestor_hashtable_entry_free(sm_memberof_cached_value *entry) ++{ ++ size_t i; ++ if (entry == NULL) { ++ return; ++ } ++ ++ /* entry[] is an array. The last element of the array contains 'valid'=0 */ ++ for (i = 0; entry[i].valid; i++) { ++ slapi_ch_free_string(&entry[i].group_dn_val); ++ slapi_ch_free_string(&entry[i].group_ndn_val); ++ slapi_ch_free_string(&entry[i].nsuniqueid_val); ++ } ++ /* Here we are at the ending element containing the key */ ++ slapi_ch_free_string(&entry[i].key); ++} ++ ++static PLHashEntry * ++sm_ancestors_cache_add(Slapi_MemberOfConfig *config, const void *key, void *value) ++{ ++ PLHashEntry *e; ++#if defined(DEBUG) ++ long int start; ++ struct timespec tsnow; ++#endif ++ sm_cache_stat.total_add++; ++ ++#if defined(DEBUG) ++ if (clock_gettime(CLOCK_REALTIME, &tsnow) != 0) { ++ start = 0; ++ } else { ++ start = tsnow.tv_nsec; ++ } ++#endif ++ ++ e = PL_HashTableAdd(config->ancestors_cache, key, value); ++ ++#if defined(DEBUG) ++ if (start) { ++ if (clock_gettime(CLOCK_REALTIME, &tsnow) == 0) { ++ sm_cache_stat.cumul_duration_add += (tsnow.tv_nsec - start); ++ } ++ } ++#endif ++ return e; ++} ++ ++/* ++ * A cache value consist of an array of all dn/ndn of the groups member_ndn_val belongs to ++ * The last element of the array has 'valid=0' ++ * the firsts elements of the array has 'valid=1' and the dn/ndn of group it belong to ++ */ ++static void ++sm_cache_ancestors(Slapi_MemberOfConfig *config, Slapi_Value **member_ndn_val, sm_memberof_get_groups_data *groups) ++{ ++ Slapi_ValueSet *groupvals = *((sm_memberof_get_groups_data *) groups)->groupvals; ++ Slapi_ValueSet *nsuniqueidvals = *((sm_memberof_get_groups_data *) groups)->nsuniqueidvals; ++ Slapi_Value *sval_dn = NULL; ++ Slapi_Value *sval_nsuniqueid = NULL; ++ Slapi_DN *sdn = NULL; ++ const char *dn = NULL; ++ const char *nsuniqueid = NULL; ++ const char *ndn = NULL; ++ const char *key = NULL; ++ char *key_copy = NULL; ++ int hint_dn = 0; ++ int hint_nsuniqueid = 0; ++ int count = 0; ++ size_t index; ++ sm_memberof_cached_value *cache_entry; ++#if MEMBEROF_CACHE_DEBUG ++ sm_memberof_cached_value *double_check; ++#endif ++ ++ if ((member_ndn_val == NULL) || (*member_ndn_val == NULL)) { ++ slapi_log_err(SLAPI_LOG_FATAL, "slapi_memberof", "sm_cache_ancestors: Fail to cache groups ancestor of unknown member\n"); ++ return; ++ } ++ ++ /* Allocate the cache entry and fill it */ ++ count = slapi_valueset_count(groupvals); ++ if (count == 0) { ++ /* There is no group containing member_ndn_val ++ * so cache the NULL value ++ */ ++ cache_entry = (sm_memberof_cached_value *) slapi_ch_calloc(2, sizeof (sm_memberof_cached_value)); ++ if (!cache_entry) { ++ slapi_log_err(SLAPI_LOG_FATAL, "slapi_memberof", "sm_cache_ancestors: Fail to cache no group are ancestor of %s\n", ++ slapi_value_get_string(*member_ndn_val)); ++ return; ++ } ++ index = 0; ++ cache_entry[index].key = NULL; /* only the last element (valid=0) contains the key */ ++ cache_entry[index].group_dn_val = NULL; ++ cache_entry[index].group_ndn_val = NULL; ++ cache_entry[index].nsuniqueid_val = NULL; ++ cache_entry[index].valid = 1; /* this entry is valid and indicate no group contain member_ndn_val */ ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_cache_ancestors: For %s cache %s/%s\n", ++ slapi_value_get_string(*member_ndn_val), ++ cache_entry[index].group_dn_val ? cache_entry[index].group_dn_val : "", ++ cache_entry[index].nsuniqueid_val ? cache_entry[index].nsuniqueid_val : ""); ++#endif ++ index++; ++ ++ } else { ++ cache_entry = (sm_memberof_cached_value *) slapi_ch_calloc(count + 1, sizeof (sm_memberof_cached_value)); ++ if (!cache_entry) { ++ slapi_log_err(SLAPI_LOG_FATAL, "slapi_memberof", "sm_cache_ancestors: Fail to cache groups ancestor of %s\n", ++ slapi_value_get_string(*member_ndn_val)); ++ return; ++ } ++ ++ /* Store the dn/ndn into the cache_entry */ ++ index = 0; ++ hint_dn = slapi_valueset_first_value(groupvals, &sval_dn); ++ hint_nsuniqueid = slapi_valueset_first_value(nsuniqueidvals, &sval_nsuniqueid); ++ while (sval_dn) { ++ /* In case of recursion the member_ndn can be present ++ * in its own ancestors. Skip it ++ */ ++ if (sm_memberof_compare(groups->config, member_ndn_val, &sval_dn)) { ++ /* add this dn/ndn even if it is NULL ++ * in fact a node belonging to no group needs to be cached ++ */ ++ dn = slapi_value_get_string(sval_dn); ++ nsuniqueid = slapi_value_get_string(sval_nsuniqueid); ++ sdn = slapi_sdn_new_dn_byval(dn); ++ ndn = slapi_sdn_get_ndn(sdn); ++ ++ cache_entry[index].key = NULL; /* only the last element (valid=0) contains the key */ ++ cache_entry[index].group_dn_val = slapi_ch_strdup(dn); ++ cache_entry[index].group_ndn_val = slapi_ch_strdup(ndn); ++ cache_entry[index].nsuniqueid_val = slapi_ch_strdup(nsuniqueid); ++ cache_entry[index].valid = 1; ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_cache_ancestors: %s/%s\n", ++ cache_entry[index].group_dn_val ? cache_entry[index].group_dn_val : "", ++ cache_entry[index].nsuniqueid_val ? cache_entry[index].nsuniqueid_val : ""); ++#endif ++ index++; ++ slapi_sdn_free(&sdn); ++ } ++ ++ hint_dn = slapi_valueset_next_value(groupvals, hint_dn, &sval_dn); ++ hint_nsuniqueid = slapi_valueset_next_value(nsuniqueidvals, hint_nsuniqueid, &sval_nsuniqueid); ++ } ++ } ++ /* This is marking the end of the cache_entry */ ++ key = slapi_value_get_string(*member_ndn_val); ++ key_copy = slapi_ch_strdup(key); ++ cache_entry[index].key = key_copy; ++ cache_entry[index].group_dn_val = NULL; ++ cache_entry[index].group_ndn_val = NULL; ++ cache_entry[index].nsuniqueid_val = NULL; ++ cache_entry[index].valid = 0; ++ ++ /* Cache the ancestor of member_ndn_val using the ++ * normalized DN key ++ */ ++#if MEMBEROF_CACHE_DEBUG ++ sm_dump_cache_entry(cache_entry, key); ++#endif ++ if (sm_ancestors_cache_add(config, (const void*) key_copy, (void *) cache_entry) == NULL) { ++ slapi_log_err(SLAPI_LOG_FATAL, "slapi_memberof", "sm_cache_ancestors: Failed to cache ancestor of %s\n", key); ++ sm_ancestor_hashtable_entry_free(cache_entry); ++ slapi_ch_free((void**) &cache_entry); ++ return; ++ } ++#if MEMBEROF_CACHE_DEBUG ++ if ((double_check = sm_ancestors_cache_lookup(config, (const void*) key)) != NULL) { ++ sm_dump_cache_entry(double_check, "read back"); ++ } ++#endif ++} ++ ++/* memberof_compare() ++ * ++ * compare two attr values ++ */ ++static int ++sm_memberof_compare(Slapi_MemberOfConfig *config, const void *a, const void *b) ++{ ++ Slapi_Value *val1; ++ Slapi_Value *val2; ++ ++ if (a == NULL && b != NULL) { ++ return 1; ++ } else if (a != NULL && b == NULL) { ++ return -1; ++ } else if (a == NULL && b == NULL) { ++ return 0; ++ } ++ val1 = *((Slapi_Value **) a); ++ val2 = *((Slapi_Value **) b); ++ ++ /* We only need to provide a Slapi_Attr here for it's syntax. We ++ * already validated all grouping attributes to use the Distinguished ++ * Name syntax, so we can safely just use the first attr. ++ */ ++ return slapi_attr_value_cmp_ext(config->dn_syntax_attr, val1, val2); ++} ++ ++/* ++ * Add in v2 the values that are in v1 ++ * If the values are already present in v2, they are skipped ++ * It does not consum the values in v1 ++ */ ++static void ++sm_merge_ancestors(Slapi_Value **member_ndn_val, sm_memberof_get_groups_data *v1, sm_memberof_get_groups_data *v2) ++{ ++ Slapi_Value *sval_dn = 0; ++ Slapi_Value *sval_ndn = 0; ++ Slapi_Value *sval_nsuniqueid = 0; ++ Slapi_Value *sval, *sval_2; ++ Slapi_DN *val_sdn = 0; ++ int hint = 0; ++ int hint_nsuniqueid = 0; ++ Slapi_MemberOfConfig *config = ((sm_memberof_get_groups_data *) v2)->config; ++ Slapi_ValueSet *v1_groupvals = *((sm_memberof_get_groups_data *) v1)->groupvals; ++ Slapi_ValueSet *v2_groupvals = *((sm_memberof_get_groups_data *) v2)->groupvals; ++ Slapi_ValueSet *v2_group_norm_vals = *((sm_memberof_get_groups_data *) v2)->group_norm_vals; ++ Slapi_ValueSet *v1_nsuniqueidvals = *((sm_memberof_get_groups_data *) v1)->nsuniqueidvals; ++ Slapi_ValueSet *v2_nsuniqueidvals = *((sm_memberof_get_groups_data *) v2)->nsuniqueidvals; ++ int merged_cnt = 0; ++ ++ hint = slapi_valueset_first_value(v1_groupvals, &sval); ++ hint_nsuniqueid = slapi_valueset_first_value(v1_nsuniqueidvals, &sval_2); ++ while (sval) { ++ if (sm_memberof_compare(config, member_ndn_val, &sval)) { ++ sval_dn = slapi_value_new_string(slapi_value_get_string(sval)); ++ sval_nsuniqueid = slapi_value_new_string(slapi_value_get_string(sval_2)); ++ if (sval_dn) { ++ /* Use the normalized dn from v1 to search it in v2 */ ++ val_sdn = slapi_sdn_new_dn_byval(slapi_value_get_string(sval_dn)); ++ sval_ndn = slapi_value_new_string(slapi_sdn_get_ndn(val_sdn)); ++ if (!slapi_valueset_find(((sm_memberof_get_groups_data *) v2)->config->dn_syntax_attr, ++ v2_group_norm_vals, sval_ndn)) { ++ /* This ancestor was not already present in v2 => Add it ++ * Using slapi_valueset_add_value it consumes val ++ * so do not free sval ++ */ ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_merge_ancestors: add %s\n", slapi_value_get_string(sval_ndn)); ++#endif ++ slapi_valueset_add_value_ext(v2_groupvals, sval_dn, SLAPI_VALUE_FLAG_PASSIN); ++ slapi_valueset_add_value_ext(v2_group_norm_vals, sval_ndn, SLAPI_VALUE_FLAG_PASSIN); ++ slapi_valueset_add_value_ext(v2_nsuniqueidvals, sval_nsuniqueid, SLAPI_VALUE_FLAG_PASSIN); ++ merged_cnt++; ++ } else { ++ /* This ancestor was already present, free sval_ndn/sval_dn that will not be consumed */ ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_merge_ancestors: skip (already present) %s\n", slapi_value_get_string(sval_ndn)); ++#endif ++ slapi_value_free(&sval_dn); ++ slapi_value_free(&sval_ndn); ++ slapi_value_free(&sval_nsuniqueid); ++ } ++ slapi_sdn_free(&val_sdn); ++ } ++ } ++ hint = slapi_valueset_next_value(v1_groupvals, hint, &sval); ++ hint_nsuniqueid = slapi_valueset_next_value(v1_nsuniqueidvals, hint_nsuniqueid, &sval_2); ++ } ++} ++ ++/* ++ * Return 1 if the entry is in the scope. ++ * For MODRDN the caller should check both the preop ++ * and postop entries. If we are moving out of, or ++ * into scope, we should process it. ++ */ ++static int ++sm_memberof_entry_in_scope(Slapi_MemberOfConfig *config, Slapi_DN *sdn) ++{ ++ if (config->entryScopeExcludeSubtrees) { ++ /* check the excludes */ ++ size_t i = 0; ++ while (config->entryScopeExcludeSubtrees[i]) { ++ if (slapi_sdn_issuffix(sdn, config->entryScopeExcludeSubtrees[i])) { ++ return 0; ++ } ++ i++; ++ } ++ } ++ if (config->entryScopes) { ++ /* check the excludes */ ++ size_t i = 0; ++ while (config->entryScopes[i]) { ++ if (slapi_sdn_issuffix(sdn, config->entryScopes[i])) { ++ return 1; ++ } ++ i++; ++ } ++ return 0; ++ } ++ return 1; ++} ++ ++/* memberof_get_groups_callback() ++ * ++ * Callback to perform work of memberof_get_groups() ++ */ ++static int ++sm_memberof_get_groups_callback(Slapi_Entry *e, void *callback_data) ++{ ++ Slapi_DN *group_sdn = slapi_entry_get_sdn(e); ++ char *group_ndn = slapi_entry_get_ndn(e); ++ char *group_dn = slapi_entry_get_dn(e); ++ const char *group_nsuniqueid = slapi_entry_get_uniqueid(e); ++ Slapi_Value *group_ndn_val = 0; ++ Slapi_Value *group_dn_val = 0; ++ Slapi_Value *group_nsuniqueid_val = 0; ++ Slapi_Value *already_seen_ndn_val = 0; ++ Slapi_ValueSet *groupvals = *((sm_memberof_get_groups_data *) callback_data)->groupvals; ++ Slapi_ValueSet *group_norm_vals = *((sm_memberof_get_groups_data *) callback_data)->group_norm_vals; ++ Slapi_ValueSet *group_nsuniqueid_vals = *((sm_memberof_get_groups_data *) callback_data)->nsuniqueidvals; ++ Slapi_ValueSet *already_seen_ndn_vals = *((sm_memberof_get_groups_data *) callback_data)->already_seen_ndn_vals; ++ Slapi_MemberOfConfig *config = ((sm_memberof_get_groups_data *) callback_data)->config; ++ int rc = 0; ++ ++ if (slapi_is_shutting_down()) { ++ rc = -1; ++ goto bail; ++ } ++ ++ if (config->maxgroups_reached) { ++ rc = -1; ++ goto bail; ++ } ++ ++ if (!groupvals || !group_norm_vals) { ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_memberof_get_groups_callback - NULL groupvals or group_norm_vals\n"); ++ rc = -1; ++ goto bail; ++ } ++ /* get the DN of the group */ ++ group_ndn_val = slapi_value_new_string(group_ndn); ++ /* group_dn is case-normalized */ ++ slapi_value_set_flags(group_ndn_val, SLAPI_ATTR_FLAG_NORMALIZED_CIS); ++ ++ /* check if e is the same as our original member entry */ ++ if (0 == sm_memberof_compare(((sm_memberof_get_groups_data *) callback_data)->config, ++ &((sm_memberof_get_groups_data *) callback_data)->memberdn_val, &group_ndn_val)) { ++ /* A recursive group caused us to find our original ++ * entry we passed to memberof_get_groups(). We just ++ * skip processing this entry. */ ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_memberof_get_groups_callback - Group recursion" ++ " detected in %s\n", ++ group_ndn); ++ slapi_value_free(&group_ndn_val); ++ ((sm_memberof_get_groups_data *) callback_data)->use_cache = PR_FALSE; ++ goto bail; ++ } ++ ++ /* Have we been here before? Note that we don't loop through all of the membership_slapiattrs ++ * in config. We only need this attribute for it's syntax so the comparison can be ++ * performed. Since all of the grouping attributes are validated to use the Dinstinguished ++ * Name syntax, we can safely just use the first group_slapiattr. */ ++ if (slapi_valueset_find( ++ ((sm_memberof_get_groups_data *) callback_data)->config->dn_syntax_attr, already_seen_ndn_vals, group_ndn_val)) { ++ /* we either hit a recursive grouping, or an entry is ++ * a member of a group through multiple paths. Either ++ * way, we can just skip processing this entry since we've ++ * already gone through this part of the grouping hierarchy. */ ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_memberof_get_groups_callback - Possible group recursion" ++ " detected in %s\n", ++ group_ndn); ++ slapi_value_free(&group_ndn_val); ++ ((sm_memberof_get_groups_data *) callback_data)->use_cache = PR_FALSE; ++ goto bail; ++ } ++ ++ /* if the group does not belong to an excluded subtree, adds it to the valueset */ ++ if (sm_memberof_entry_in_scope(config, group_sdn)) { ++ /* Push group_dn_val into the valueset. This memory is now owned ++ * by the valueset. */ ++ slapi_valueset_add_value_ext(group_norm_vals, group_ndn_val, SLAPI_VALUE_FLAG_PASSIN); ++ ++ group_dn_val = slapi_value_new_string(group_dn); ++ slapi_valueset_add_value_ext(groupvals, group_dn_val, SLAPI_VALUE_FLAG_PASSIN); ++ ++ group_nsuniqueid_val = slapi_value_new_string(group_nsuniqueid); ++ slapi_valueset_add_value_ext(group_nsuniqueid_vals, group_nsuniqueid_val, SLAPI_VALUE_FLAG_PASSIN); ++ ++ /* push this ndn to detect group recursion */ ++ already_seen_ndn_val = slapi_value_new_string(group_ndn); ++ slapi_valueset_add_value_ext(already_seen_ndn_vals, already_seen_ndn_val, SLAPI_VALUE_FLAG_PASSIN); ++ ++ config->current_maxgroup++; ++ ++ /* Check we did not hit the maxgroup */ ++ if ((config->maxgroups) && ++ (config->current_maxgroup >= config->maxgroups)) { ++ char *msg = "slapi_memberof: sm_memberof_get_groups_callback result set truncated because of maxgroups limit (from computation)"; ++ sm_report_error_msg(config, msg); ++ config->maxgroups_reached = PR_TRUE; ++ rc = -1; ++ goto bail; ++ } ++ } ++ if (config->recurse && (config->maxgroups_reached == PR_FALSE)) { ++ /* now recurse to find ancestors groups of e */ ++ sm_memberof_get_groups_r(((sm_memberof_get_groups_data *) callback_data)->config, ++ group_sdn, callback_data); ++ } ++ ++bail: ++ return rc; ++} ++ ++/* Should be called at shutdown only */ ++void ++slapi_memberof_free_memberof_plugin_config() ++{ ++ struct memberof_plugin_config *sav; ++ if (memberof_config == NULL) { ++ return; ++ } ++ sav = memberof_config; ++ memberof_config = NULL; ++ slapi_ch_array_free(sav->groupattrs); ++ sav->groupattrs = NULL; ++ ++ slapi_ch_array_free(sav->include_scope); ++ sav->include_scope = NULL; ++ ++ slapi_ch_array_free(sav->exclude_scope); ++ sav->exclude_scope = NULL; ++ ++ slapi_ch_free_string(&sav->memberof_attr); ++ slapi_ch_free((void **) &sav); ++} ++ ++int ++slapi_memberof_load_memberof_plugin_config() ++{ ++ Slapi_PBlock *entry_pb = NULL; ++ Slapi_Entry *config_entry = NULL; ++ Slapi_DN *config_sdn = NULL; ++ int rc; ++ char *attrs[8]; ++ const char *allBackends = NULL; ++ const char *skip_nested = NULL; ++ const char *enabled = NULL; ++ ++ /* if already loaded, we are done */ ++ if (memberof_config) { ++ return 0; ++ } ++ memberof_config = (struct memberof_plugin_config *) slapi_ch_calloc(1, sizeof (struct memberof_plugin_config)); ++ ++ ++ /* Retrieve the config entry */ ++ config_sdn = slapi_sdn_new_normdn_byref(MEMBEROF_CONFIG_DN); ++ attrs[0] = MEMBEROF_GOUP_ATTR; /* e.g. member, uniquemember,... */ ++ attrs[1] = MEMBEROF_ATTR; /* e.g. memberof */ ++ attrs[2] = MEMBEROF_ALL_BACKENDS; ++ attrs[3] = MEMBEROF_INCLUDE_SCOPE; ++ attrs[4] = MEMBEROF_EXLCUDE_SCOPE; ++ attrs[5] = MEMBEROF_SKIP_NESTED; ++ attrs[6] = MEMBEROF_ENABLED; ++ attrs[7] = NULL; ++ rc = slapi_search_get_entry(&entry_pb, config_sdn, attrs, &config_entry, plugin_get_default_component_id()); ++ slapi_sdn_free(&config_sdn); ++ ++ if (rc != LDAP_SUCCESS || config_entry == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "get_memberof_plugin_config - Failed to retrieve configuration entry %s: %d\n", ++ MEMBEROF_CONFIG_DN, rc); ++ slapi_search_get_entry_done(&entry_pb); ++ return (-1); ++ } ++ memberof_config->groupattrs = slapi_entry_attr_get_charray(config_entry, MEMBEROF_GOUP_ATTR); ++ memberof_config->memberof_attr = slapi_entry_attr_get_charptr(config_entry, MEMBEROF_ATTR); ++ allBackends = slapi_entry_attr_get_ref(config_entry, MEMBEROF_ALL_BACKENDS); ++ if (allBackends) { ++ if (strcasecmp(allBackends, "on") == 0) { ++ memberof_config->all_backends = PR_TRUE; ++ } else { ++ memberof_config->all_backends = PR_FALSE; ++ } ++ } else { ++ memberof_config->all_backends = PR_FALSE; ++ } ++ skip_nested = slapi_entry_attr_get_ref(config_entry, MEMBEROF_SKIP_NESTED); ++ if (skip_nested) { ++ if (strcasecmp(skip_nested, "on") == 0) { ++ memberof_config->skip_nested = PR_TRUE; ++ } else { ++ memberof_config->skip_nested = PR_FALSE; ++ } ++ } else { ++ memberof_config->skip_nested = PR_FALSE; ++ } ++ enabled = slapi_entry_attr_get_ref(config_entry, MEMBEROF_ENABLED); ++ if (enabled) { ++ if (strcasecmp(enabled, "on") == 0) { ++ memberof_config->enabled = PR_TRUE; ++ } else { ++ memberof_config->enabled = PR_FALSE; ++ } ++ } else { ++ memberof_config->enabled = PR_FALSE; ++ } ++ memberof_config->include_scope = slapi_entry_attr_get_charray(config_entry, MEMBEROF_INCLUDE_SCOPE); ++ memberof_config->exclude_scope = slapi_entry_attr_get_charray(config_entry, MEMBEROF_EXLCUDE_SCOPE); ++ ++ slapi_search_get_entry_done(&entry_pb); ++ return (rc); ++} ++ ++static void ++sm_report_error_msg(Slapi_MemberOfConfig *config, char* msg) ++{ ++ int32_t len; ++ ++ if ((config->error_msg == NULL) || (config->errot_msg_lenght == 0) || (msg == NULL)) { ++ return; ++ } ++ len = strlen(msg); ++ if ((config->errot_msg_lenght - 1) < len) { ++ len = config->errot_msg_lenght - 1; ++ } ++ strncpy(config->error_msg, msg, len); ++ config->error_msg[len] = '\0'; ++} ++ ++static int ++sm_entry_get_groups(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_ValueSet *groupvals, Slapi_ValueSet *nsuniqueidvals) ++{ ++ Slapi_PBlock *member_pb = NULL; ++ Slapi_PBlock *group_pb = NULL; ++ char **groups_dn; ++ Slapi_Entry *member_entry = NULL; ++ Slapi_Entry *group_entry = NULL; ++ Slapi_DN *group_sdn; ++ Slapi_Value *sval; ++ const char *nsuniqueid; ++ char *attrs[2]; ++ int rc = 0; ++ ++ /* Retrieve the 'memberof' from the target entry */ ++ attrs[0] = (char *) config->memberof_attr; ++ attrs[1] = NULL; ++ rc = slapi_search_get_entry(&member_pb, member_sdn, attrs, &member_entry, plugin_get_default_component_id()); ++ if (rc != LDAP_SUCCESS || member_entry == NULL) { ++ char *msg = "slapi_memberof: fails to retrieve the target entry"; ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_entry_get_groups - Failed to retrieve target entry %s: %d\n", ++ slapi_sdn_get_ndn(member_sdn), rc); ++ slapi_search_get_entry_done(&member_pb); ++ sm_report_error_msg(config, msg); ++ return (-1); ++ } ++ ++ /* For each group that the target entry is memberof, retrieve its dn/nsuniqueid */ ++ groups_dn = slapi_entry_attr_get_charray(member_entry, config->memberof_attr); ++ attrs[0] = "nsuniqueid"; ++ attrs[1] = NULL; ++ for (size_t i = 0; groups_dn && groups_dn[i]; i++) { ++ if ((config->maxgroups > 0) && (i >= config->maxgroups)) { ++ char *msg = "slapi_memberof: result set truncated because of maxgroups limit (from memberof)"; ++ sm_report_error_msg(config, msg); ++ config->maxgroups_reached = PR_TRUE; ++ rc = -1; ++ goto common; ++ } ++ group_pb = NULL; ++ group_sdn = slapi_sdn_new_dn_byval(groups_dn[i]); ++ rc = slapi_search_get_entry(&group_pb, group_sdn, attrs, &group_entry, plugin_get_default_component_id()); ++ if (rc != LDAP_SUCCESS || member_entry == NULL) { ++ char *msg = "slapi_memberof: fails to retrieve a group"; ++ sm_report_error_msg(config, msg); ++ ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_entry_get_groups - Failed to retrieve target entry %s: %d\n", ++ slapi_sdn_get_ndn(group_sdn), rc); ++ slapi_sdn_free(&group_sdn); ++ slapi_ch_array_free(groups_dn); ++ rc = -1; ++ goto common; ++ } ++ ++ /* Now we retrieve a group */ ++ /* add its nsuniqueid to the valuset */ ++ nsuniqueid = slapi_entry_attr_get_ref(group_entry, (const char*) "nsuniqueid"); ++ if (nsuniqueid) { ++ sval = slapi_value_new_string(nsuniqueid); ++ } else { ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_entry_get_groups - Failed to retrieve the nsuniqueid of the target entry %s\n", ++ slapi_sdn_get_ndn(group_sdn)); ++ sval = slapi_value_new_string("unknown-nsuniqueid"); ++ } ++ slapi_valueset_add_value_ext(nsuniqueidvals, sval, SLAPI_VALUE_FLAG_PASSIN); ++ /* add its dn to the valuset */ ++ sval = slapi_value_new_string(slapi_sdn_get_ndn(group_sdn)); ++ slapi_valueset_add_value_ext(groupvals, sval, SLAPI_VALUE_FLAG_PASSIN); ++ ++ ++ slapi_sdn_free(&group_sdn); ++ slapi_search_get_entry_done(&group_pb); ++ } ++ ++common: ++ slapi_ch_array_free(groups_dn); ++ slapi_search_get_entry_done(&member_pb); ++ return rc; ++} ++ ++static PRBool ++sm_compare_memberof_config(const char *memberof_attr, char **groupattrs, PRBool all_backends, PRBool skip_nested, Slapi_DN **include_scope, Slapi_DN **exclude_scope, PRBool enabled_only) ++{ ++ 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"); ++ 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", ++ memberof_config->enabled ? "SUCCEEDS" : "FAILS"); ++ if (memberof_config->enabled) { ++ return PR_TRUE; ++ } else { ++ return PR_FALSE; ++ } ++ } ++ ++ /* 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", ++ all_backends, memberof_config->all_backends, skip_nested, memberof_config->skip_nested); ++ return PR_FALSE; ++ } ++ ++ /* Check that we are dealing with the same membership attribute ++ * e.g. 'memberof' ++ */ ++ 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", ++ "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); ++ return PR_FALSE; ++ } ++ ++ /* Check that the membership attributes are identical to the one ++ * in the memberof config. e.g. 'member', 'uniquemember'.. ++ */ ++ 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"); ++ 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", ++ "sm_compare_memberof_config: fails because requested group attribute '%s' is not configured\n", ++ groupattrs[cnt1]); ++ return PR_FALSE; ++ } ++ } ++ 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"); ++ 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"); ++ 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"); ++ 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_sdn_get_ndn(include_scope[cnt1])); ++ return PR_FALSE; ++ } ++ } ++ 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"); ++ 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"); ++ 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"); ++ 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_sdn_get_ndn(exclude_scope[cnt1])); ++ return PR_FALSE; ++ } ++ } ++ 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"); ++ return PR_FALSE; ++ } ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_compare_memberof_config: succeeds. requested options match config\n"); ++ return PR_TRUE; ++} ++ ++static void ++sm_add_ancestors_cbdata(sm_memberof_cached_value *ancestors, void *callback_data) ++{ ++ Slapi_Value *sval; ++ size_t val_index; ++ Slapi_ValueSet *group_norm_vals_cbdata = *((sm_memberof_get_groups_data *) callback_data)->group_norm_vals; ++ Slapi_ValueSet *group_vals_cbdata = *((sm_memberof_get_groups_data *) callback_data)->groupvals; ++ Slapi_ValueSet *nsuniqueid_vals_cbdata = *((sm_memberof_get_groups_data *) callback_data)->nsuniqueidvals; ++ Slapi_Value *memberdn_val = ((sm_memberof_get_groups_data *) callback_data)->memberdn_val; ++ int added_group; ++ PRBool empty_ancestor = PR_FALSE; ++ ++ for (val_index = 0, added_group = 0; ancestors[val_index].valid; val_index++) { ++ /* For each of its ancestor (not already present) add it to callback_data */ ++ ++ if (ancestors[val_index].group_ndn_val == NULL) { ++ /* This is a node with no ancestor ++ * ancestors should only contains this empty valid value ++ * but just in case let the loop continue instead of a break ++ */ ++ empty_ancestor = PR_TRUE; ++ continue; ++ } ++ ++ sval = slapi_value_new_string(ancestors[val_index].group_ndn_val); ++ if (sval) { ++ if (!slapi_valueset_find( ++ ((sm_memberof_get_groups_data *) callback_data)->config->dn_syntax_attr, group_norm_vals_cbdata, sval)) { ++ /* This ancestor was not already present in the callback data ++ * => Add it to the callback_data ++ * Using slapi_valueset_add_value it consumes sval ++ * so do not free sval ++ */ ++ slapi_valueset_add_value_ext(group_norm_vals_cbdata, sval, SLAPI_VALUE_FLAG_PASSIN); ++ sval = slapi_value_new_string(ancestors[val_index].group_dn_val); ++ slapi_valueset_add_value_ext(group_vals_cbdata, sval, SLAPI_VALUE_FLAG_PASSIN); ++ sval = slapi_value_new_string(ancestors[val_index].nsuniqueid_val); ++ slapi_valueset_add_value_ext(nsuniqueid_vals_cbdata, sval, SLAPI_VALUE_FLAG_PASSIN); ++ added_group++; ++ } else { ++ /* This ancestor was already present, free sval that will not be consumed */ ++ slapi_value_free(&sval); ++ } ++ } ++ } ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", ++ "sm_add_ancestors_cbdata: Ancestors of %s contained %ld groups. %d added. %s\n", ++ slapi_value_get_string(memberdn_val), val_index, added_group, ++ empty_ancestor ? "no ancestors" : ""); ++} ++ ++/* ++ * Does a callback search of "type=dn" under the db suffix that "dn" is in, ++ * unless all_backends is set, then we look at all the backends. If "dn" ++ * is a user, you'd want "type" to be "member". If "dn" is a group, you ++ * could want type to be either "member" or "memberOf" depending on the case. ++ */ ++static int ++sm_memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn, Slapi_MemberOfConfig *config, char **types, plugin_search_entry_callback callback, void *callback_data, int *cached, PRBool use_grp_cache) ++{ ++ Slapi_PBlock *search_pb = NULL; ++ Slapi_DN *base_sdn = NULL; ++ Slapi_Backend *be = NULL; ++ char *escaped_filter_val; ++ char *filter_str = NULL; ++ char *cookie = NULL; ++ int all_backends = config->allBackends; ++ int dn_len = slapi_sdn_get_ndn_len(sdn); ++ int free_it = 0; ++ int rc = 0; ++ ++ *cached = 0; ++ ++ if (!sm_memberof_entry_in_scope(config, sdn)) { ++ return (rc); ++ } ++ ++ /* This flags indicates memberof_call_foreach_dn is called to retrieve ancestors (groups). ++ * To improve performance, it can use a cache. (it will not in case of circular groups) ++ * When this flag is true it means no circular group are detected (so far) so we can use the cache ++ */ ++ if (use_grp_cache) { ++ /* Here we will retrieve the ancestor of sdn. ++ * The key access is the normalized sdn ++ * This is done through recursive internal searches of parents ++ * If the ancestors of sdn are already cached, just use ++ * this value ++ */ ++ sm_memberof_cached_value *ht_grp = NULL; ++ const char *ndn = slapi_sdn_get_ndn(sdn); ++ ++ ht_grp = sm_ancestors_cache_lookup(config, (const void *) ndn); ++ if (ht_grp) { ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_memberof_call_foreach_dn: Ancestors of %s already cached (%p)\n", ndn, ht_grp); ++#endif ++ sm_add_ancestors_cbdata(ht_grp, callback_data); ++ *cached = 1; ++ return (rc); ++ } ++ } ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_PLUGIN, "slapi_memberof", "sm_memberof_call_foreach_dn: Ancestors of %s not cached\n", slapi_sdn_get_ndn(sdn)); ++#endif ++ ++ /* Escape the dn, and build the search filter. */ ++ escaped_filter_val = slapi_escape_filter_value((char *) slapi_sdn_get_dn(sdn), dn_len); ++ if (escaped_filter_val) { ++ free_it = 1; ++ } else { ++ escaped_filter_val = (char *) slapi_sdn_get_dn(sdn); ++ } ++ ++ for (size_t i = 0; (types[i] && (config->maxgroups_reached == PR_FALSE)); i++) { ++ /* Triggers one internal search per membership attribute. ++ * Assuming the attribute is indexed (eq), the search will ++ * bypass the evaluation of the filter (nsslapd-search-bypass-filter-test) ++ * against the candidates. This is important to bypass the filter ++ * because on large valueset (static group) it is very expensive ++ */ ++ filter_str = slapi_ch_smprintf("(%s=%s%s)", types[i], config->subtree_search ? "*" : "", escaped_filter_val); ++ ++ be = slapi_get_first_backend(&cookie); ++ while ((config->maxgroups_reached == PR_FALSE) && be) { ++ PRBool do_suffix_search = PR_TRUE; ++ ++ if (!all_backends) { ++ be = slapi_be_select(sdn); ++ if (be == NULL) { ++ break; ++ } ++ } ++ if ((base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0)) == NULL) { ++ if (!all_backends) { ++ break; ++ } else { ++ /* its ok, goto the next backend */ ++ be = slapi_get_next_backend(cookie); ++ continue; ++ } ++ } ++ ++ search_pb = slapi_pblock_new(); ++ if ((config->entryScopes && config->entryScopes[0]) || ++ (config->entryScopeExcludeSubtrees && config->entryScopeExcludeSubtrees[0])) { ++ if (sm_memberof_entry_in_scope(config, base_sdn)) { ++ /* do nothing, entry scope is spanning ++ * multiple suffixes, start at suffix */ ++ } else if (config->entryScopes) { ++ for (size_t i = 0; config->entryScopes[i]; i++) { ++ if (slapi_sdn_issuffix(config->entryScopes[i], base_sdn)) { ++ /* Search each include scope */ ++ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(config->entryScopes[i]), ++ LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_callback_pb(search_pb, callback_data, 0, callback, 0); ++ /* We already did the search for this backend, don't ++ * do it again when we fall through */ ++ do_suffix_search = PR_FALSE; ++ } ++ } ++ } else if (!all_backends) { ++ slapi_pblock_destroy(search_pb); ++ break; ++ } else { ++ /* its ok, goto the next backend */ ++ be = slapi_get_next_backend(cookie); ++ slapi_pblock_destroy(search_pb); ++ continue; ++ } ++ } ++ ++ if (do_suffix_search) { ++ slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn), ++ LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0, ++ plugin_get_default_component_id(), 0); ++ slapi_search_internal_callback_pb(search_pb, callback_data, 0, callback, 0); ++ slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc); ++ if (rc != LDAP_SUCCESS) { ++ slapi_pblock_destroy(search_pb); ++ break; ++ } ++ } ++ ++ if (!all_backends) { ++ slapi_pblock_destroy(search_pb); ++ break; ++ } ++ ++ be = slapi_get_next_backend(cookie); ++ slapi_pblock_destroy(search_pb); ++ } ++ slapi_ch_free((void **) &cookie); ++ slapi_ch_free_string(&filter_str); ++ } ++ ++ if (free_it) { ++ slapi_ch_free_string(&escaped_filter_val); ++ } ++ return rc; ++} ++ ++static int ++sm_memberof_get_groups_r(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, sm_memberof_get_groups_data *data) ++{ ++ Slapi_ValueSet *groupvals = slapi_valueset_new(); ++ Slapi_ValueSet *group_norm_vals = slapi_valueset_new(); ++ Slapi_ValueSet *nsuniqueidvals = slapi_valueset_new(); ++ Slapi_Value *member_ndn_val = ++ slapi_value_new_string(slapi_sdn_get_ndn(member_sdn)); ++ int rc; ++ int cached = 0; ++ ++ slapi_value_set_flags(member_ndn_val, SLAPI_ATTR_FLAG_NORMALIZED_CIS); ++ ++ sm_memberof_get_groups_data member_data = {config, member_ndn_val, &groupvals, &group_norm_vals, &nsuniqueidvals, data->already_seen_ndn_vals, data->use_cache}; ++ ++ /* Search for any grouping attributes that point to memberdn. ++ * For each match, add it to the list, recurse and do same search */ ++#if MEMBEROF_CACHE_DEBUG ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "sm_memberof_get_groups_r: Ancestors of %s\n", slapi_sdn_get_dn(member_sdn)); ++#endif ++ rc = sm_memberof_call_foreach_dn(NULL, member_sdn, config, config->groupattrs, ++ sm_memberof_get_groups_callback, &member_data, ++ &cached, member_data.use_cache); ++ ++ sm_merge_ancestors(&member_ndn_val, &member_data, data); ++ if (!cached && member_data.use_cache) ++ sm_cache_ancestors(config, &member_ndn_val, &member_data); ++ ++ slapi_value_free(&member_ndn_val); ++ slapi_valueset_free(groupvals); ++ slapi_valueset_free(group_norm_vals); ++ slapi_valueset_free(nsuniqueidvals); ++ ++ ++ return rc; ++} ++ ++static PRIntn ++sm_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 ++sm_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 ++sm_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 ++ */ ++static PLHashTable * ++sm_hashtable_new(int usetxn) ++{ ++ if (!usetxn) { ++ return NULL; ++ } ++ ++ return PL_NewHashTable(1000, ++ sm_memberof_hash_fn, ++ sm_memberof_hash_compare_keys, ++ sm_memberof_hash_compare_values, NULL, NULL); ++} ++ ++/* this function called for each hash node during hash destruction */ ++static PRIntn ++sm_ancestor_hashtable_remove(PLHashEntry *he, PRIntn index __attribute__((unused)), void *arg __attribute__((unused))) ++{ ++ sm_memberof_cached_value *group_ancestor_array; ++ ++ if (he == NULL) { ++ return HT_ENUMERATE_NEXT; ++ } ++ group_ancestor_array = (sm_memberof_cached_value *) he->value; ++ sm_ancestor_hashtable_entry_free(group_ancestor_array); ++ slapi_ch_free((void **) &group_ancestor_array); ++ ++ return HT_ENUMERATE_REMOVE; ++} ++ ++static void ++sm_ancestor_hashtable_empty(Slapi_MemberOfConfig *config, char *msg) ++{ ++ if (config->ancestors_cache) { ++ PL_HashTableEnumerateEntries(config->ancestors_cache, sm_ancestor_hashtable_remove, msg); ++ } ++} ++ ++int ++slapi_memberof(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_MemberOfResult *result) ++{ ++ Slapi_ValueSet *groupvals; ++ Slapi_ValueSet *nsuniqueidvals; ++ Slapi_ValueSet *group_norm_vals; ++ Slapi_ValueSet *already_seen_ndn_vals; ++ Slapi_Value *memberdn_val; ++ Slapi_Attr *membership_slapiattrs; ++ int32_t rc = 0; ++ ++ if (config == NULL || member_sdn == NULL || result == NULL) { ++ return -1; ++ } ++ config->maxgroups_reached = PR_FALSE; ++ config->current_maxgroup = 0; ++ if (config->error_msg) { ++ memset(config->error_msg, 0, config->errot_msg_lenght); ++ strcpy(config->error_msg, "no error msg"); ++ } ++ groupvals = slapi_valueset_new(); ++ nsuniqueidvals = slapi_valueset_new(); ++ if (config->flag == MEMBEROF_REUSE_ONLY) { ++ if (sm_compare_memberof_config(NULL, NULL, PR_FALSE, PR_FALSE, ++ NULL, NULL, PR_TRUE)) { ++ /* Whatever the configuration of memberof plugin as long ++ * as it is enabled, return the groups referenced in the target entry ++ */ ++ rc = sm_entry_get_groups(config, member_sdn, groupvals, nsuniqueidvals); ++ } else { ++ slapi_log_err(SLAPI_LOG_ERR, "slapi_memberof", "memberof plugin is not enabled, with MEMBEROF_REUSE_ONLY return empty result"); ++ } ++ } else if ((config->flag == MEMBEROF_REUSE_IF_POSSIBLE) && ++ sm_compare_memberof_config(config->memberof_attr, ++ config->groupattrs, ++ config->allBackends, ++ !config->recurse, ++ config->entryScopes, ++ config->entryScopeExcludeSubtrees, PR_FALSE)) { ++ /* If the configuration of memberof plugin match the requested config ++ * (and the plugin is enabled), return the groups referenced in the target entry ++ */ ++ rc = sm_entry_get_groups(config, member_sdn, groupvals, nsuniqueidvals); ++ } else { ++ /* This is RECOMPUTE mode or memberof plugin config does not satisfy ++ * the requested config, then recompute the membership ++ */ ++ group_norm_vals = slapi_valueset_new(); ++ already_seen_ndn_vals = slapi_valueset_new(); ++ memberdn_val = slapi_value_new_string(slapi_sdn_get_ndn(member_sdn)); ++ membership_slapiattrs = slapi_attr_new(); ++ slapi_attr_init(membership_slapiattrs, config->groupattrs[0]); ++ config->memberof_attr = "memberof"; /* used to check if the shortcut of memberof plugin is possible */ ++ config->dn_syntax_attr = membership_slapiattrs; /* all groupattrs are DN syntax, get the syntax from any of them */ ++ config->maxgroups_reached = PR_FALSE; ++ ++ slapi_value_set_flags(memberdn_val, SLAPI_ATTR_FLAG_NORMALIZED_CIS); ++ ++ config->ancestors_cache = sm_hashtable_new(1); ++ sm_memberof_get_groups_data data = {config, memberdn_val, &groupvals, &group_norm_vals, &nsuniqueidvals, &already_seen_ndn_vals, PR_TRUE}; ++ ++ rc = sm_memberof_get_groups_r(config, member_sdn, &data); ++ ++ slapi_attr_free(&membership_slapiattrs); ++ slapi_value_free(&memberdn_val); ++ slapi_valueset_free(group_norm_vals); ++ slapi_valueset_free(already_seen_ndn_vals); ++ ++ if (config->ancestors_cache) { ++ sm_ancestor_hashtable_empty(config, "memberof_free_config empty group_ancestors_hashtable"); ++ PL_HashTableDestroy(config->ancestors_cache); ++ config->ancestors_cache = NULL; ++ } ++ } ++ ++ result->dn_vals = groupvals; ++ result->nsuniqueid_vals = nsuniqueidvals; ++ ++ return rc; ++} +diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h +index 79d04e418..d9d697a49 100644 +--- a/ldap/servers/slapd/slapi-plugin.h ++++ b/ldap/servers/slapd/slapi-plugin.h +@@ -34,6 +34,7 @@ extern "C" { + #include "prprf.h" + #include "nspr.h" + #include ++#include + + #ifdef __GNUC__ + #define __ATTRIBUTE__(x) __attribute__(x) +@@ -8444,6 +8445,40 @@ int32_t slapi_search_get_entry(Slapi_PBlock **pb, Slapi_DN *dn, char **attrs, Sl + */ + void slapi_search_get_entry_done(Slapi_PBlock **pb); + ++/* Those definitions are used to implement slapi_memberof() */ ++typedef enum { ++ MEMBEROF_REUSE_ONLY, ++ MEMBEROF_REUSE_IF_POSSIBLE, ++ MEMBEROF_RECOMPUTE ++} memberof_flag_t; ++ ++typedef struct _slapi_memberofresult { ++ Slapi_ValueSet *nsuniqueid_vals; ++ Slapi_ValueSet *dn_vals; ++ PRBool maxgroups_reached; /* flag is true if the number of groups hit the max limit */ ++} Slapi_MemberOfResult; ++ ++typedef struct _slapi_memberofconfig ++{ ++ char **groupattrs; ++ PRBool subtree_search; ++ int allBackends; ++ Slapi_DN **entryScopes; ++ Slapi_DN **entryScopeExcludeSubtrees; ++ PRBool recurse; ++ int maxgroups; ++ memberof_flag_t flag; ++ char *error_msg; ++ int errot_msg_lenght; ++ int entryScopeCount; /* private to slapi_memberof */ ++ int entryExcludeScopeCount; /* private to slapi_memberof */ ++ PRBool maxgroups_reached; /* private to slapi_memberof */ ++ const char *memberof_attr; /* private to slapi_memberof */ ++ Slapi_Attr *dn_syntax_attr; /* private to slapi_memberof */ ++ PLHashTable *ancestors_cache; /* private to slapi_memberof */ ++ int current_maxgroup; /* private to slapi_memberof */ ++} Slapi_MemberOfConfig; ++ + #ifdef __cplusplus + } + #endif +diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h +index bbf443dfc..17eedc2de 100644 +--- a/ldap/servers/slapd/slapi-private.h ++++ b/ldap/servers/slapd/slapi-private.h +@@ -1258,6 +1258,11 @@ int mkdir_p(char *dir, unsigned int mode); + const char *ldif_getline_ro( const char **next); + void dup_ldif_line(struct berval *copy, const char *line, const char *endline); + ++/* slapi-memberof.c */ ++int slapi_memberof(Slapi_MemberOfConfig *config, Slapi_DN *member_sdn, Slapi_MemberOfResult *result); ++void slapi_memberof_free_memberof_plugin_config(void); ++int slapi_memberof_load_memberof_plugin_config(void); ++ + /* lenstr stuff */ + + typedef struct _lenstr +diff --git a/ldap/servers/slapd/test-plugins/test_slapi_memberof.c b/ldap/servers/slapd/test-plugins/test_slapi_memberof.c +new file mode 100644 +index 000000000..56626866b +--- /dev/null ++++ b/ldap/servers/slapd/test-plugins/test_slapi_memberof.c +@@ -0,0 +1,476 @@ ++/** BEGIN COPYRIGHT BLOCK ++ * Copyright (C) 2007 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 ++ ++ ++/** ++ * Distributed Numeric Assignment plug-in ++ */ ++#include ++#include ++#include ++#include ++#include "portable.h" ++#include "slap.h" ++#include "nspr.h" ++#include "slapi-private.h" ++#include "slapi-plugin.h" ++#include "prclist.h" ++ ++#include ++ ++ ++ ++#define TEST_SLAPI_MEMBEROF_FEATURE_DESC "test slapi_memberof" ++#define TEST_SLAPI_MEMBEROF_EXOP_FEATURE_DESC "test slapi_memberof Extension Request" ++#define TEST_SLAPI_MEMBEROF_PLUGIN_DESC "test slapi_memberof plugin" ++#define TEST_SLAPI_MEMBEROF_EXOP_DESC "test slapi_memberof extop plugin" ++ ++ ++#define TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM "test-slapi_memberof-plugin" ++ ++#define TEST_SLAPI_MEMBEROF_MEMBER_DN "slapimemberOfMemberDN" ++#define TEST_SLAPI_MEMBEROF_GROUP_ATTR "slapimemberOfGroupAttr" ++#define TEST_SLAPI_MEMBEROF_ATTR "slapimemberOfAttr" ++#define TEST_SLAPI_MEMBEROF_BACKEND_ATTR "slapimemberOfAllBackends" ++#define TEST_SLAPI_MEMBEROF_ENTRY_SCOPE_ATTR "slapimemberOfEntryScope" ++#define TEST_SLAPI_MEMBEROF_ENTRY_SCOPE_EXCLUDE_SUBTREE "slapimemberOfEntryScopeExcludeSubtree" ++#define TEST_SLAPI_MEMBEROF_SKIP_NESTED_ATTR "slapimemberOfSkipNested" ++#define TEST_SLAPI_MEMBEROF_MAXGROUP "slapimemberOfMaxGroup" ++#define TEST_SLAPI_MEMBEROF_FLAG "slapimemberOfFlag" ++ ++#define MEMBEROF_RECOMPUTE_STR "MEMBEROF_RECOMPUTE" ++#define MEMBEROF_REUSE_IF_POSSIBLE_STR "MEMBEROF_REUSE_IF_POSSIBLE" ++#define MEMBEROF_REUSE_ONLY_STR "MEMBEROF_REUSE_ONLY" ++ ++static Slapi_PluginDesc pdesc = {TEST_SLAPI_MEMBEROF_FEATURE_DESC, ++ "389 Project - test plugin", ++ "RHDS 9.3", ++ TEST_SLAPI_MEMBEROF_PLUGIN_DESC}; ++ ++static Slapi_PluginDesc exop_pdesc = {TEST_SLAPI_MEMBEROF_EXOP_FEATURE_DESC, ++ "389 Project - test plugin", ++ "RHDS 9.3", ++ TEST_SLAPI_MEMBEROF_EXOP_DESC}; ++ ++typedef struct test_slapi_memberof_config ++{ ++ char *member_dn; ++ Slapi_DN *sdn_member_dn; ++ char **groupattrs; ++ char *memberof_attr; ++ int32_t maxgroup; ++ memberof_flag_t flag; ++ PRBool allBackends; ++ PRBool skip_nested; ++ char **entryScopes; ++ Slapi_DN **sdn_entryScopes; ++ char **entryScopeExcludeSubtrees; ++ Slapi_DN **sdn_entryScopeExcludeSubtrees; ++} Test_Slapi_MemberOf_Config; ++static Test_Slapi_MemberOf_Config theConfig = {0}; ++static void *_PluginID = NULL; ++ ++#define TEST_SLAPI_MEMBEROF_EXOP_REQUEST_OID "2.3.4.5.113730.6.7.1" ++#define TEST_SLAPI_MEMBEROF_EXOP_RESPONSE_OID "2.3.4.5.113730.6.7.2" ++static char *test_slapi_memberof_exop_oid_list[] = { ++ TEST_SLAPI_MEMBEROF_EXOP_REQUEST_OID, ++ NULL}; ++ ++ ++int test_slapi_memberof_init(Slapi_PBlock *pb); ++static int test_slapi_memberof_exop_init(Slapi_PBlock *pb); ++static int test_slapi_memberof_extend_exop(Slapi_PBlock *pb); ++static int test_slapi_memberof_start(Slapi_PBlock *pb); ++static int test_slapi_memberof_close(Slapi_PBlock *pb __attribute__((unused))); ++ ++ ++/** ++ * Plugin identity mgmt ++ */ ++void ++setPluginID(void *pluginID) ++{ ++ _PluginID = pluginID; ++} ++ ++void * ++getPluginID(void) ++{ ++ return _PluginID; ++} ++ ++ ++/* ++ test_slapi_memberof_init plugin init function ++*/ ++int ++test_slapi_memberof_init(Slapi_PBlock *pb) ++{ ++ int status = SLAPI_PLUGIN_SUCCESS; ++ char *plugin_identity = NULL; ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "--> test_slapi_memberof_init\n"); ++ ++ /** ++ * Store the plugin identity for later use. ++ * Used for internal operations ++ */ ++ ++ slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_identity); ++ PR_ASSERT(plugin_identity); ++ setPluginID(plugin_identity); ++ ++ if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, ++ SLAPI_PLUGIN_VERSION_01) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_START_FN, ++ (void *)test_slapi_memberof_start) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_CLOSE_FN, ++ (void *)test_slapi_memberof_close) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, ++ (void *)&pdesc) != 0) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_init - Failed to register plugin\n"); ++ status = SLAPI_PLUGIN_FAILURE; ++ } ++ ++ if ((status == SLAPI_PLUGIN_SUCCESS) && ++ /* the range extension extended operation */ ++ slapi_register_plugin("extendedop", /* op type */ ++ 1, /* Enabled */ ++ "test_slapi_memberof_init", /* this function desc */ ++ test_slapi_memberof_exop_init, /* init func for exop */ ++ TEST_SLAPI_MEMBEROF_EXOP_DESC, /* plugin desc */ ++ NULL, /* ? */ ++ plugin_identity /* access control */ ++ )) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_init - Failed to register plugin\n"); ++ status = SLAPI_PLUGIN_FAILURE; ++ } ++ ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "<-- test_slapi_memberof_init\n"); ++ return status; ++} ++ ++ ++ ++ ++static int ++test_slapi_memberof_exop_init(Slapi_PBlock *pb) ++{ ++ int status = SLAPI_PLUGIN_SUCCESS; ++ ++ if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, ++ SLAPI_PLUGIN_VERSION_01) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, ++ (void *)&exop_pdesc) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_OIDLIST, ++ (void *)test_slapi_memberof_exop_oid_list) != 0 || ++ slapi_pblock_set(pb, SLAPI_PLUGIN_EXT_OP_FN, ++ (void *)test_slapi_memberof_extend_exop) != 0) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_exop_init - Failed to register plugin\n"); ++ status = SLAPI_PLUGIN_FAILURE; ++ } ++ ++ return status; ++} ++ ++static int ++test_slapi_memberof_start(Slapi_PBlock *pb) ++{ ++ Slapi_Entry *config_e = NULL; /* entry containing plugin config */ ++ char **groupattrs = NULL; ++ char *memberof_attr = NULL; ++ char *member_dn = NULL; ++ int maxgroup = 0; ++ const char *allBackends = NULL; ++ const char *skip_nested = NULL; ++ char **entryScopes = NULL; ++ char **entryScopeExcludeSubtrees = NULL; ++ char *flag; ++ size_t i; ++ ++ if (slapi_pblock_get(pb, SLAPI_ADD_ENTRY, &config_e) != 0) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_start - Missing config entry\n"); ++ return SLAPI_PLUGIN_FAILURE; ++ } ++ groupattrs = slapi_entry_attr_get_charray(config_e, TEST_SLAPI_MEMBEROF_GROUP_ATTR); ++ memberof_attr = slapi_entry_attr_get_charptr(config_e, TEST_SLAPI_MEMBEROF_ATTR); ++ member_dn = slapi_entry_attr_get_charptr(config_e, TEST_SLAPI_MEMBEROF_MEMBER_DN); ++ allBackends = slapi_entry_attr_get_ref(config_e, TEST_SLAPI_MEMBEROF_BACKEND_ATTR); ++ skip_nested = slapi_entry_attr_get_ref(config_e, TEST_SLAPI_MEMBEROF_SKIP_NESTED_ATTR); ++ entryScopes = slapi_entry_attr_get_charray(config_e, TEST_SLAPI_MEMBEROF_ENTRY_SCOPE_ATTR); ++ entryScopeExcludeSubtrees = slapi_entry_attr_get_charray(config_e, TEST_SLAPI_MEMBEROF_ENTRY_SCOPE_EXCLUDE_SUBTREE); ++ maxgroup = slapi_entry_attr_get_int(config_e, TEST_SLAPI_MEMBEROF_MAXGROUP); ++ flag = slapi_entry_attr_get_charptr(config_e, TEST_SLAPI_MEMBEROF_FLAG); ++ ++ theConfig.groupattrs = groupattrs; ++ theConfig.member_dn = member_dn; ++ theConfig.sdn_member_dn = slapi_sdn_new_dn_byval(member_dn); ++ theConfig.memberof_attr = memberof_attr; ++ if (skip_nested) { ++ if (strcasecmp(skip_nested, "on") == 0) { ++ theConfig.skip_nested = PR_TRUE; ++ } else { ++ theConfig.skip_nested = PR_FALSE; ++ } ++ } else { ++ theConfig.skip_nested = PR_FALSE; ++ } ++ ++ if (allBackends) { ++ if (strcasecmp(allBackends, "on") == 0) { ++ theConfig.allBackends = PR_TRUE; ++ } else { ++ theConfig.allBackends = PR_FALSE; ++ } ++ } else { ++ theConfig.allBackends = PR_FALSE; ++ } ++ theConfig.entryScopes = entryScopes; ++ for (i = 0; entryScopes && entryScopes[i]; i++); ++ theConfig.sdn_entryScopes = (Slapi_DN **) slapi_ch_calloc(sizeof(Slapi_DN *), i + 1); ++ for (i = 0; entryScopes && entryScopes[i]; i++) { ++ theConfig.sdn_entryScopes[i] = slapi_sdn_new_dn_byval((const char *)entryScopes[i]); ++ } ++ ++ theConfig.entryScopeExcludeSubtrees = entryScopeExcludeSubtrees; ++ for (i = 0; entryScopeExcludeSubtrees && entryScopeExcludeSubtrees[i]; i++); ++ theConfig.sdn_entryScopeExcludeSubtrees = (Slapi_DN **) slapi_ch_calloc(sizeof(Slapi_DN *), i + 1); ++ for (i = 0; entryScopeExcludeSubtrees && entryScopeExcludeSubtrees[i]; i++) { ++ theConfig.sdn_entryScopeExcludeSubtrees[i] = slapi_sdn_new_dn_byval((const char *)entryScopeExcludeSubtrees[i]); ++ } ++ theConfig.maxgroup = maxgroup; ++ ++ /* By default we are recomputing the membership MEMBEROF_RECOMPUTE */ ++ if (flag == NULL) { ++ theConfig.flag = MEMBEROF_RECOMPUTE; ++ } else if (strcasecmp(flag, MEMBEROF_RECOMPUTE_STR) == 0) { ++ theConfig.flag = MEMBEROF_RECOMPUTE; ++ } else if (strcasecmp(flag, MEMBEROF_REUSE_IF_POSSIBLE_STR) == 0) { ++ theConfig.flag = MEMBEROF_REUSE_IF_POSSIBLE; ++ } else if (strcasecmp(flag, MEMBEROF_REUSE_ONLY_STR) == 0) { ++ theConfig.flag = MEMBEROF_REUSE_ONLY; ++ } else { ++ theConfig.flag = MEMBEROF_RECOMPUTE; ++ } ++ ++ return SLAPI_PLUGIN_SUCCESS; ++} ++ ++static int ++test_slapi_memberof_close(Slapi_PBlock *pb __attribute__((unused))) ++{ ++ return SLAPI_PLUGIN_SUCCESS; ++} ++ ++/**************************************************** ++ * Test Slapi_memberof Extended Operation ++ ***************************************************/ ++static int ++test_slapi_memberof_extend_exop(Slapi_PBlock *pb) ++{ ++ char *oid = NULL; ++ int ret = SLAPI_PLUGIN_EXTENDED_NOT_HANDLED; ++ int i; ++ int idx = 0; ++ int count; ++ char error_buffer[1024] = {0}; ++ int32_t error_buffer_size; ++ Slapi_MemberOfConfig config = {0}; ++ Slapi_MemberOfResult groupvals = {0}; ++ Slapi_DN *target_sdn; ++ BerElement *req_bere = NULL; ++ struct berval *reqdata = NULL; ++ char *req_dn = NULL; ++ BerElement *respber = NULL; ++ struct berval *respdata = NULL; ++ Slapi_Value *v; ++ struct berval **returned_bervals = NULL; ++ char **returned_array = NULL; ++ ++ error_buffer_size = sizeof(error_buffer); ++ ++ if (!slapi_plugin_running(pb)) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - plugin not started\n"); ++ return ret; ++ } ++ ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "--> test_slapi_memberof_extend_exop\n"); ++ /* Fetch the request OID */ ++ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_OID, &oid); ++ if (!oid) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - Unable to retrieve request OID.\n"); ++ return ret; ++ } ++ ++ /* decode the exop */ ++ slapi_pblock_get(pb, SLAPI_EXT_OP_REQ_VALUE, &reqdata); ++ if (BV_HAS_DATA(reqdata)) { ++ req_bere = ber_init(reqdata); ++ if (req_bere == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - Failed to decode/init the DN from the request.\n"); ++ return ret; ++ } ++ ber_scanf(req_bere, "a", &req_dn); ++ ber_free(req_bere, 1); ++ req_bere = NULL; ++ } ++ ++ if (req_dn) { ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "--> Target entry is %s\n", req_dn); ++ } else { ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "--> Target entry is (fallback) %s\n", theConfig.member_dn); ++ } ++ ++ /* Make sure the request OID is correct. */ ++ if (strcmp(oid, TEST_SLAPI_MEMBEROF_EXOP_REQUEST_OID) != 0) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - Received incorrect request OID.\n"); ++ return ret; ++ } ++ ++ /* Checking the incoming attributes */ ++ if (theConfig.member_dn == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - missing %s attribute (e.g. 'uid=user0,ou=people,dc=example,dc=com').\n", ++ TEST_SLAPI_MEMBEROF_MEMBER_DN); ++ strncpy(error_buffer, "missing slapimemberOfMemberDN", error_buffer_size - 1); ++ goto skip_it; ++ } ++ if (theConfig.memberof_attr == NULL) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - missing %s attribute (e.g. 'memberof').\n", ++ TEST_SLAPI_MEMBEROF_ATTR); ++ strncpy(error_buffer, "missing slapimemberOfAttr", error_buffer_size - 1); ++ goto skip_it; ++ } ++ if ((theConfig.groupattrs == NULL) || (theConfig.groupattrs[0] == NULL)) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - missing %s attribute (e.g. 'member' or 'manager').\n", ++ TEST_SLAPI_MEMBEROF_GROUP_ATTR); ++ strncpy(error_buffer, "missing slapimemberOfGroupAttr", error_buffer_size - 1); ++ goto skip_it; ++ } ++ if ((theConfig.entryScopes == NULL) || (theConfig.entryScopes[0] == NULL)) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - missing %s attribute (e.g. 'dc=example,dc=com').\n", ++ TEST_SLAPI_MEMBEROF_ENTRY_SCOPE_ATTR); ++ strncpy(error_buffer, "missing slapimemberOfEntryScope", error_buffer_size - 1); ++ goto skip_it; ++ } ++ ++ config.memberof_attr = (const char *) theConfig.memberof_attr; ++ config.groupattrs = theConfig.groupattrs; ++ config.allBackends = theConfig.allBackends; ++ config.recurse = (! theConfig.skip_nested); ++ ++ config.entryScopes = theConfig.sdn_entryScopes; ++ config.entryScopeExcludeSubtrees = theConfig.sdn_entryScopeExcludeSubtrees; ++ config.maxgroups = theConfig.maxgroup; ++ config.flag = theConfig.flag; ++ config.error_msg = error_buffer; ++ config.errot_msg_lenght = error_buffer_size; ++ config.subtree_search = PR_FALSE; ++ if (req_dn) { ++ target_sdn = slapi_sdn_new_dn_byval(req_dn); ++ } else { ++ target_sdn = theConfig.sdn_member_dn; ++ } ++ ret = slapi_memberof(&config, target_sdn, &groupvals); ++ if (req_dn) { ++ slapi_sdn_free(&target_sdn); ++ } ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - slapi_memberof -> %d).\n", ++ ret); ++ /* Just fo tracing purpose log the memberships in error logs */ ++ for (i = slapi_valueset_first_value(groupvals.dn_vals, &v); ++ i != -1; ++ i = slapi_valueset_next_value(groupvals.dn_vals, i, &v)) { ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - slapi_memberof found %s.\n", ++ slapi_value_get_string(v)); ++ } ++ ++ ++skip_it: ++ if ((respber = ber_alloc()) == NULL) { ++ ret = LDAP_NO_MEMORY; ++ goto free_and_return; ++ } ++ count = slapi_valueset_count(groupvals.dn_vals); ++ if (count) { ++ returned_bervals = (struct berval **)slapi_ch_malloc(sizeof(struct berval *) * (count + 1)); ++ returned_bervals[count] = NULL; ++ for (i = slapi_valueset_first_value(groupvals.dn_vals, &v), idx = 0; ++ i != -1; ++ i = slapi_valueset_next_value(groupvals.dn_vals, i, &v), idx++) { ++ returned_bervals[idx] = (struct berval *)slapi_ch_malloc(sizeof(struct berval)); ++ returned_bervals[idx]->bv_val = slapi_ch_strdup(slapi_value_get_string(v)); ++ returned_bervals[idx]->bv_len = strlen(slapi_value_get_string(v)); ++ } ++ returned_array = (char **) slapi_ch_malloc(sizeof(char *) * (count + 1)); ++ returned_array[count] = NULL; ++ for (i = slapi_valueset_first_value(groupvals.dn_vals, &v), idx = 0; ++ i != -1; ++ i = slapi_valueset_next_value(groupvals.dn_vals, i, &v), idx++) { ++ returned_array[idx] = slapi_ch_strdup(slapi_value_get_string(v)); ++ } ++ if (LBER_ERROR == (ber_printf(respber, "[V]", returned_bervals))) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - Unable to encode exop response.\n"); ++ ber_free(respber, 1); ++ ret = LDAP_ENCODING_ERROR; ++ goto free_and_return; ++ } ++ } else { ++ if (LBER_ERROR == (ber_printf(respber, "{s}", error_buffer))) { ++ slapi_log_err(SLAPI_LOG_ERR, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "test_slapi_memberof_extend_exop - Unable to encode exop response.\n"); ++ ber_free(respber, 1); ++ ret = LDAP_ENCODING_ERROR; ++ goto free_and_return; ++ } ++ } ++ ber_flatten(respber, &respdata); ++ ber_free(respber, 1); ++ ++ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_OID, TEST_SLAPI_MEMBEROF_EXOP_RESPONSE_OID); ++ slapi_pblock_set(pb, SLAPI_EXT_OP_RET_VALUE, respdata); ++ ++ /* send the response ourselves */ ++ slapi_send_ldap_result(pb, LDAP_SUCCESS, NULL, NULL, 0, NULL); ++ ret = SLAPI_PLUGIN_EXTENDED_SENT_RESULT; ++ ber_bvfree(respdata); ++ ++free_and_return: ++ ++ if (returned_bervals) ++ ber_bvecfree(returned_bervals); ++ slapi_log_err(SLAPI_LOG_NOTICE, TEST_SLAPI_MEMBEROF_PLUGIN_SUBSYSTEM, ++ "<-- test_slapi_memberof_extend_exop\n"); ++ ++ return ret; ++} ++ +-- +2.41.0 + diff --git a/0003-Issue-5551-Almost-empty-and-not-loaded-ns-slapd-high.patch b/0003-Issue-5551-Almost-empty-and-not-loaded-ns-slapd-high.patch new file mode 100644 index 0000000..ed6380a --- /dev/null +++ b/0003-Issue-5551-Almost-empty-and-not-loaded-ns-slapd-high.patch @@ -0,0 +1,33 @@ +From 528c1e6b31e97bdd8ed43f04d56584f220ace403 Mon Sep 17 00:00:00 2001 +From: John Obaterspok +Date: Thu, 29 Jun 2023 19:19:27 +0200 +Subject: [PATCH 3/5] Issue 5551 - Almost empty and not loaded ns-slapd high + cpu load + +Bug Description: stracing the ns-slapd process one can see nanosleep gets called a lot as we only sleep for 1ms + +Fix Description: Increasing the sleep time from 1ms to 500ms and the cpu usage will drop drop a few percent and strace will be more usable + +relates: https://github.com/389ds/389-ds-base/issues/5551 + +Reviewd by: @mreynolds389 +--- + ldap/servers/slapd/daemon.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c +index f0526684d..c5ad5ee0f 100644 +--- a/ldap/servers/slapd/daemon.c ++++ b/ldap/servers/slapd/daemon.c +@@ -1119,7 +1119,7 @@ slapd_daemon(daemon_ports_t *ports) + /* The meat of the operation is in a loop on a call to select */ + while (!g_get_shutdown()) { + +- usleep(1000); ++ usleep(500 * 1000); + } + /* We get here when the server is shutting down */ + /* Do what we have to do before death */ +-- +2.41.0 + diff --git a/0004-Issue-4551-Paged-search-impacts-performance-5838.patch b/0004-Issue-4551-Paged-search-impacts-performance-5838.patch new file mode 100644 index 0000000..7c042de --- /dev/null +++ b/0004-Issue-4551-Paged-search-impacts-performance-5838.patch @@ -0,0 +1,611 @@ +From e8e73bf1434f8f459b9cdaee001fb2b6f161a6eb Mon Sep 17 00:00:00 2001 +From: progier389 +Date: Tue, 18 Jul 2023 11:17:07 +0200 +Subject: [PATCH 4/5] Issue 4551 - Paged search impacts performance (#5838) + +* Issue 4551 - Paged search impacts performance + +Problem: +Having a script looping doing a search with paged result impact greatly the performance of other clients +(for example ldclt bind+search rate decreased by 80% in the test case) + +Cause: +Page result field in connection were protected by the connection mutex that is also used by the listener thread, in some cases this cause contention that delays the handling of new operations + +Solution: +Do not rely on the connection mutex to protect the page result context but on a dedicated array of locks. + +(cherry picked from commit 3c510e0a26e321949b552b5e8c887634d9d7e63e) +--- + ldap/servers/slapd/daemon.c | 1 + + ldap/servers/slapd/main.c | 2 + + ldap/servers/slapd/opshared.c | 20 +++-- + ldap/servers/slapd/pagedresults.c | 134 ++++++++++++++++++------------ + ldap/servers/slapd/proto-slap.h | 3 + + 5 files changed, 98 insertions(+), 62 deletions(-) + +diff --git a/ldap/servers/slapd/daemon.c b/ldap/servers/slapd/daemon.c +index c5ad5ee0f..01168275f 100644 +--- a/ldap/servers/slapd/daemon.c ++++ b/ldap/servers/slapd/daemon.c +@@ -1211,6 +1211,7 @@ slapd_daemon(daemon_ports_t *ports) + slapi_log_err(SLAPI_LOG_TRACE, "slapd_daemon", + "slapd shutting down - waiting for backends to close down\n"); + ++ pageresult_lock_cleanup(); + eq_stop(); /* deprecated */ + eq_stop_rel(); + if (!in_referral_mode) { +diff --git a/ldap/servers/slapd/main.c b/ldap/servers/slapd/main.c +index f24650547..deae0af3b 100644 +--- a/ldap/servers/slapd/main.c ++++ b/ldap/servers/slapd/main.c +@@ -1007,6 +1007,7 @@ main(int argc, char **argv) + eq_init_rel(); /* must be done before plugins started */ + + ps_init_psearch_system(); /* must come before plugin_startall() */ ++ pageresult_lock_init(); + + + /* initialize UniqueID generator - must be done once backends are started +@@ -2265,6 +2266,7 @@ slapd_exemode_db2ldif(int argc, char **argv, struct main_config *mcfg) + eq_init_rel(); /* must be done before plugins started */ + + ps_init_psearch_system(); /* must come before plugin_startall() */ ++ pageresult_lock_init(); + plugin_startall(argc, argv, plugin_list); + eq_start(); /* must be done after plugins started - DEPRECATED*/ + eq_start_rel(); /* must be done after plugins started */ +diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c +index 789bd2e4f..a842d4249 100644 +--- a/ldap/servers/slapd/opshared.c ++++ b/ldap/servers/slapd/opshared.c +@@ -271,6 +271,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + int pr_idx = -1; + Slapi_DN *orig_sdn = NULL; + int free_sdn = 0; ++ pthread_mutex_t *pagedresults_mutex = NULL; + + be_list[0] = NULL; + referral_list[0] = NULL; +@@ -576,6 +577,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + int32_t tlimit; + slapi_pblock_get(pb, SLAPI_SEARCH_TIMELIMIT, &tlimit); + pagedresults_set_timelimit(pb_conn, operation, (time_t)tlimit, pr_idx); ++ pagedresults_mutex = pageresult_lock_get_addr(pb_conn); + } + + /* +@@ -696,7 +698,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + * In async paged result case, the search result might be released + * by other theads. We need to double check it in the locked region. + */ +- pthread_mutex_lock(&(pb_conn->c_mutex)); ++ pthread_mutex_lock(pagedresults_mutex); + pr_search_result = pagedresults_get_search_result(pb_conn, operation, 1 /*locked*/, pr_idx); + if (pr_search_result) { + if (pagedresults_is_abandoned_or_notavailable(pb_conn, 1 /*locked*/, pr_idx)) { +@@ -704,7 +706,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + /* Previous operation was abandoned and the simplepaged object is not in use. */ + send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL); + rc = LDAP_SUCCESS; +- pthread_mutex_unlock(&(pb_conn->c_mutex)); ++ pthread_mutex_unlock(pagedresults_mutex); + goto free_and_return; + } else { + slapi_pblock_set(pb, SLAPI_SEARCH_RESULT_SET, pr_search_result); +@@ -718,7 +720,7 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + pr_stat = PAGEDRESULTS_SEARCH_END; + rc = LDAP_SUCCESS; + } +- pthread_mutex_unlock(&(pb_conn->c_mutex)); ++ pthread_mutex_unlock(pagedresults_mutex); + pagedresults_unlock(pb_conn, pr_idx); + + if ((PAGEDRESULTS_SEARCH_END == pr_stat) || (0 == pnentries)) { +@@ -843,10 +845,10 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + /* PAGED RESULTS */ + if (op_is_pagedresults(operation)) { + /* cleanup the slot */ +- pthread_mutex_lock(&(pb_conn->c_mutex)); ++ pthread_mutex_lock(pagedresults_mutex); + pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx); + rc = pagedresults_set_current_be(pb_conn, NULL, pr_idx, 1); +- pthread_mutex_unlock(&(pb_conn->c_mutex)); ++ pthread_mutex_unlock(pagedresults_mutex); + } + if (1 == flag_no_such_object) { + break; +@@ -887,11 +889,11 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + slapi_pblock_get(pb, SLAPI_SEARCH_RESULT_SET, &sr); + if ((PAGEDRESULTS_SEARCH_END == pr_stat) || (0 == pnentries)) { + /* no more entries, but at least another backend */ +- pthread_mutex_lock(&(pb_conn->c_mutex)); ++ pthread_mutex_lock(pagedresults_mutex); + pagedresults_set_search_result(pb_conn, operation, NULL, 1, pr_idx); + be->be_search_results_release(&sr); + rc = pagedresults_set_current_be(pb_conn, next_be, pr_idx, 1); +- pthread_mutex_unlock(&(pb_conn->c_mutex)); ++ pthread_mutex_unlock(pagedresults_mutex); + pr_stat = PAGEDRESULTS_SEARCH_END; /* make sure stat is SEARCH_END */ + if (NULL == next_be) { + /* no more entries && no more backends */ +@@ -919,9 +921,9 @@ op_shared_search(Slapi_PBlock *pb, int send_result) + next_be = NULL; /* to break the loop */ + if (operation->o_status & SLAPI_OP_STATUS_ABANDONED) { + /* It turned out this search was abandoned. */ +- pthread_mutex_lock(&(pb_conn->c_mutex)); ++ pthread_mutex_lock(pagedresults_mutex); + pagedresults_free_one_msgid_nolock(pb_conn, operation->o_msgid); +- pthread_mutex_unlock(&(pb_conn->c_mutex)); ++ pthread_mutex_unlock(pagedresults_mutex); + /* paged-results-request was abandoned; making an empty cookie. */ + pagedresults_set_response_control(pb, 0, estimate, -1, pr_idx); + send_ldap_result(pb, 0, NULL, "Simple Paged Results Search abandoned", 0, NULL); +diff --git a/ldap/servers/slapd/pagedresults.c b/ldap/servers/slapd/pagedresults.c +index 54aa086e8..fc15f6bec 100644 +--- a/ldap/servers/slapd/pagedresults.c ++++ b/ldap/servers/slapd/pagedresults.c +@@ -12,6 +12,34 @@ + + #include "slap.h" + ++#define LOCK_HASH_SIZE 997 /* Should be a prime number */ ++ ++static pthread_mutex_t *lock_hash = NULL; ++ ++void ++pageresult_lock_init() ++{ ++ lock_hash = (pthread_mutex_t *)slapi_ch_calloc(LOCK_HASH_SIZE, sizeof(pthread_mutex_t)); ++ for (size_t i=0; ic_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + /* the ber encoding is no longer needed */ + ber_free(ber, 1); + if (cookie.bv_len <= 0) { +@@ -206,7 +234,7 @@ bail: + } + } + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_parse_control_value", + "<= idx %d\n", *index); +@@ -300,7 +328,7 @@ pagedresults_free_one(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", + "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (conn->c_pagedresults.prl_count <= 0) { + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", + "conn=%" PRIu64 " paged requests list count is %d\n", +@@ -311,7 +339,7 @@ pagedresults_free_one(Connection *conn, Operation *op, int index) + conn->c_pagedresults.prl_count--; + rc = 0; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_free_one", "<= %d\n", rc); +@@ -319,7 +347,7 @@ pagedresults_free_one(Connection *conn, Operation *op, int index) + } + + /* +- * Used for abandoning - conn->c_mutex is already locked in do_abandone. ++ * Used for abandoning - pageresult_lock_get_addr(conn) is already locked in do_abandone. + */ + int + pagedresults_free_one_msgid_nolock(Connection *conn, ber_int_t msgid) +@@ -363,11 +391,11 @@ pagedresults_get_current_be(Connection *conn, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_current_be", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + be = conn->c_pagedresults.prl_list[index].pr_current_be; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_current_be", "<= %p\n", be); +@@ -382,13 +410,13 @@ pagedresults_set_current_be(Connection *conn, Slapi_Backend *be, int index, int + "pagedresults_set_current_be", "=> idx=%d\n", index); + if (conn && (index > -1)) { + if (!nolock) +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_current_be = be; + } + rc = 0; + if (!nolock) +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_current_be", "<= %d\n", rc); +@@ -407,13 +435,13 @@ pagedresults_get_search_result(Connection *conn, Operation *op, int locked, int + locked ? "locked" : "not locked", index); + if (conn && (index > -1)) { + if (!locked) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + } + if (index < conn->c_pagedresults.prl_maxlen) { + sr = conn->c_pagedresults.prl_list[index].pr_search_result_set; + } + if (!locked) { +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + } + slapi_log_err(SLAPI_LOG_TRACE, +@@ -433,7 +461,7 @@ pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int lo + index, sr); + if (conn && (index > -1)) { + if (!locked) +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + PagedResults *prp = conn->c_pagedresults.prl_list + index; + if (!(prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED) || !sr) { +@@ -443,7 +471,7 @@ pagedresults_set_search_result(Connection *conn, Operation *op, void *sr, int lo + rc = 0; + } + if (!locked) +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_search_result", "=> %d\n", rc); +@@ -460,11 +488,11 @@ pagedresults_get_search_result_count(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_search_result_count", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + count = conn->c_pagedresults.prl_list[index].pr_search_result_count; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_search_result_count", "<= %d\n", count); +@@ -481,11 +509,11 @@ pagedresults_set_search_result_count(Connection *conn, Operation *op, int count, + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_search_result_count", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_search_result_count = count; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, +@@ -506,11 +534,11 @@ pagedresults_get_search_result_set_size_estimate(Connection *conn, + "pagedresults_get_search_result_set_size_estimate", + "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + count = conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_search_result_set_size_estimate", "<= %d\n", +@@ -532,11 +560,11 @@ pagedresults_set_search_result_set_size_estimate(Connection *conn, + "pagedresults_set_search_result_set_size_estimate", + "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_search_result_set_size_estimate = count; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, +@@ -555,11 +583,11 @@ pagedresults_get_with_sort(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_with_sort", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + flags = conn->c_pagedresults.prl_list[index].pr_flags & CONN_FLAG_PAGEDRESULTS_WITH_SORT; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_with_sort", "<= %d\n", flags); +@@ -576,14 +604,14 @@ pagedresults_set_with_sort(Connection *conn, Operation *op, int flags, int index + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_with_sort", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + if (flags & OP_FLAG_SERVER_SIDE_SORTING) { + conn->c_pagedresults.prl_list[index].pr_flags |= + CONN_FLAG_PAGEDRESULTS_WITH_SORT; + } + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_set_with_sort", "<= %d\n", rc); +@@ -600,11 +628,11 @@ pagedresults_get_unindexed(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_unindexed", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + flags = conn->c_pagedresults.prl_list[index].pr_flags & CONN_FLAG_PAGEDRESULTS_UNINDEXED; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_unindexed", "<= %d\n", flags); +@@ -621,12 +649,12 @@ pagedresults_set_unindexed(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_unindexed", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_flags |= + CONN_FLAG_PAGEDRESULTS_UNINDEXED; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, +@@ -644,11 +672,11 @@ pagedresults_get_sort_result_code(Connection *conn, Operation *op, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_sort_result_code", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + code = conn->c_pagedresults.prl_list[index].pr_sort_result_code; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_get_sort_result_code", "<= %d\n", code); +@@ -665,11 +693,11 @@ pagedresults_set_sort_result_code(Connection *conn, Operation *op, int code, int + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_sort_result_code", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_sort_result_code = code; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, +@@ -687,11 +715,11 @@ pagedresults_set_timelimit(Connection *conn, Operation *op, time_t timelimit, in + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_set_timelimit", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + slapi_timespec_expire_at(timelimit, &(conn->c_pagedresults.prl_list[index].pr_timelimit_hr)); + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + rc = 0; + } + slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_set_timelimit", "<= %d\n", rc); +@@ -746,7 +774,7 @@ pagedresults_cleanup(Connection *conn, int needlock) + } + + if (needlock) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + } + for (i = 0; conn->c_pagedresults.prl_list && + i < conn->c_pagedresults.prl_maxlen; +@@ -765,7 +793,7 @@ pagedresults_cleanup(Connection *conn, int needlock) + } + conn->c_pagedresults.prl_count = 0; + if (needlock) { +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + /* slapi_log_err(SLAPI_LOG_TRACE, "pagedresults_cleanup", "<= %d\n", rc); */ + return rc; +@@ -789,7 +817,7 @@ pagedresults_cleanup_all(Connection *conn, int needlock) + } + + if (needlock) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + } + for (i = 0; conn->c_pagedresults.prl_list && + i < conn->c_pagedresults.prl_maxlen; +@@ -809,7 +837,7 @@ pagedresults_cleanup_all(Connection *conn, int needlock) + conn->c_pagedresults.prl_maxlen = 0; + conn->c_pagedresults.prl_count = 0; + if (needlock) { +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + return rc; + } +@@ -827,7 +855,7 @@ pagedresults_check_or_set_processing(Connection *conn, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_check_or_set_processing", "=>\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + ret = (conn->c_pagedresults.prl_list[index].pr_flags & + CONN_FLAG_PAGEDRESULTS_PROCESSING); +@@ -835,7 +863,7 @@ pagedresults_check_or_set_processing(Connection *conn, int index) + conn->c_pagedresults.prl_list[index].pr_flags |= + CONN_FLAG_PAGEDRESULTS_PROCESSING; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_check_or_set_processing", "<= %d\n", ret); +@@ -854,7 +882,7 @@ pagedresults_reset_processing(Connection *conn, int index) + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_reset_processing", "=> idx=%d\n", index); + if (conn && (index > -1)) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + ret = (conn->c_pagedresults.prl_list[index].pr_flags & + CONN_FLAG_PAGEDRESULTS_PROCESSING); +@@ -862,7 +890,7 @@ pagedresults_reset_processing(Connection *conn, int index) + conn->c_pagedresults.prl_list[index].pr_flags &= + ~CONN_FLAG_PAGEDRESULTS_PROCESSING; + } +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + slapi_log_err(SLAPI_LOG_TRACE, + "pagedresults_reset_processing", "<= %d\n", ret); +@@ -881,7 +909,7 @@ pagedresults_reset_processing(Connection *conn, int index) + * Do not return timed out here. But let the next request take care the + * timedout slot(s). + * +- * must be called within conn->c_mutex ++ * must be called within pageresult_lock_get_addr(conn) + */ + int + pagedresults_is_timedout_nolock(Connection *conn) +@@ -908,7 +936,7 @@ pagedresults_is_timedout_nolock(Connection *conn) + + /* + * reset all timeout +- * must be called within conn->c_mutex ++ * must be called within pageresult_lock_get_addr(conn) + */ + int + pagedresults_reset_timedout_nolock(Connection *conn) +@@ -973,9 +1001,9 @@ pagedresults_lock(Connection *conn, int index) + if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) { + return; + } +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + prp = conn->c_pagedresults.prl_list + index; +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + if (prp->pr_mutex) { + PR_Lock(prp->pr_mutex); + } +@@ -989,9 +1017,9 @@ pagedresults_unlock(Connection *conn, int index) + if (!conn || (index < 0) || (index >= conn->c_pagedresults.prl_maxlen)) { + return; + } +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + prp = conn->c_pagedresults.prl_list + index; +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + if (prp->pr_mutex) { + PR_Unlock(prp->pr_mutex); + } +@@ -1006,11 +1034,11 @@ pagedresults_is_abandoned_or_notavailable(Connection *conn, int locked, int inde + return 1; /* not abandoned, but do not want to proceed paged results op. */ + } + if (!locked) { +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + } + prp = conn->c_pagedresults.prl_list + index; + if (!locked) { +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + return prp->pr_flags & CONN_FLAG_PAGEDRESULTS_ABANDONED; + } +@@ -1035,13 +1063,13 @@ pagedresults_set_search_result_pb(Slapi_PBlock *pb, void *sr, int locked) + "pagedresults_set_search_result_pb", "=> idx=%d, sr=%p\n", index, sr); + if (conn && (index > -1)) { + if (!locked) +- pthread_mutex_lock(&(conn->c_mutex)); ++ pthread_mutex_lock(pageresult_lock_get_addr(conn)); + if (index < conn->c_pagedresults.prl_maxlen) { + conn->c_pagedresults.prl_list[index].pr_search_result_set = sr; + rc = 0; + } + if (!locked) { +- pthread_mutex_unlock(&(conn->c_mutex)); ++ pthread_mutex_unlock(pageresult_lock_get_addr(conn)); + } + } + slapi_log_err(SLAPI_LOG_TRACE, +diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h +index e8d3aaccd..43abc033f 100644 +--- a/ldap/servers/slapd/proto-slap.h ++++ b/ldap/servers/slapd/proto-slap.h +@@ -1582,6 +1582,9 @@ int slapd_do_all_nss_ssl_init(int slapd_exemode, int importexport_encrypt, int s + /* + * pagedresults.c + */ ++void pageresult_lock_init(); ++void pageresult_lock_cleanup(); ++pthread_mutex_t *pageresult_lock_get_addr(Connection *conn); + int pagedresults_parse_control_value(Slapi_PBlock *pb, struct berval *psbvp, ber_int_t *pagesize, int *index, Slapi_Backend *be); + void pagedresults_set_response_control(Slapi_PBlock *pb, int iscritical, ber_int_t estimate, int curr_search_count, int index); + Slapi_Backend *pagedresults_get_current_be(Connection *conn, int index); +-- +2.41.0 + diff --git a/389-ds-base.spec b/389-ds-base.spec index 2527073..e582f8e 100644 --- a/389-ds-base.spec +++ b/389-ds-base.spec @@ -47,7 +47,7 @@ ExcludeArch: i686 Summary: 389 Directory Server (base) Name: 389-ds-base Version: 2.3.4 -Release: 2%{?dist} +Release: 3%{?dist} License: GPLv3+ and ASL 2.0 and MIT URL: https://www.port389.org Conflicts: selinux-policy-base < 3.9.8 @@ -296,7 +296,10 @@ Source2: %{name}-devel.README Source3: https://github.com/jemalloc/%{jemalloc_name}/releases/download/%{jemalloc_ver}/%{jemalloc_name}-%{jemalloc_ver}.tar.bz2 %endif Source4: 389-ds-base.sysusers - +Patch01: 0001-Issue-5722-RFE-When-a-filter-contains-nsrole-improve.patch +Patch02: 0002-Issue-5156-RFE-that-implement-slapi_memberof-5694.patch +Patch03: 0003-Issue-5551-Almost-empty-and-not-loaded-ns-slapd-high.patch +Patch04: 0004-Issue-4551-Paged-search-impacts-performance-5838.patch %description 389 Directory Server is an LDAPv3 compliant server. The base package includes @@ -738,6 +741,12 @@ exit 0 %endif %changelog +* Mon Jul 24 2023 Mark Reynolds - 2.3.4-3 +- Bump version to 2.3.4-3 +- Resolves: rhbz#2189954 - RFE Improve reponse time to filters containing 'nsrole' +- Resolves: rhbz#2189946 - RFE support of slapi_memberof for plugins/core server +- Resolves: rhbz#1974242 - Paged search impacts performance + * Fri May 19 2023 Mark Reynolds - 2.3.4-2 - Bump version to 2.3.4-2 - Resolves: rhbz#2188627 - Fix license