Blame SOURCES/0000-Ticket-50236-memberOf-should-be-more-robust.patch

26521d
From 933b46f1f434ac7c11e155611c91f21e31c4d6f7 Mon Sep 17 00:00:00 2001
26521d
From: Mark Reynolds <mreynolds@redhat.com>
26521d
Date: Thu, 21 Feb 2019 16:58:05 -0500
26521d
Subject: [PATCH] Ticket 50236 - memberOf should be more robust
26521d
26521d
Bug Description:  When doing a modrdn, or any memberOf update, if the entry
26521d
                  already has the memberOf attribute with the same value
26521d
                  the operation is incorrectly rejected.
26521d
26521d
Fix Description:  If we get an error 20 (type or value exists) return success.
26521d
26521d
                  Also fixed a coding mistake that causes the wrong error
26521d
                  code to be returned.  This also required fixing the CI
26521d
                  test to check for the new correct errro code.
26521d
26521d
https://pagure.io/389-ds-base/issue/50236
26521d
26521d
Reviewed by:  firstyear, spichugi, and tbordaz (Thanks!!!)
26521d
---
26521d
 .../suites/memberof_plugin/regression_test.py | 48 ++++++++++---------
26521d
 ldap/servers/plugins/memberof/memberof.c      | 35 ++++++++++----
26521d
 2 files changed, 53 insertions(+), 30 deletions(-)
26521d
26521d
diff --git a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
26521d
index 2b0c4aec2..9d0ce35ed 100644
26521d
--- a/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
26521d
+++ b/dirsrvtests/tests/suites/memberof_plugin/regression_test.py
26521d
@@ -11,13 +11,12 @@ import pytest
26521d
 import os
26521d
 import time
26521d
 import ldap
26521d
-import subprocess
26521d
 from random import sample
26521d
 from lib389.utils import ds_is_older, ensure_list_bytes, ensure_bytes, ensure_str
26521d
 from lib389.topologies import topology_m1h1c1 as topo, topology_st, topology_m2 as topo_m2
26521d
 from lib389._constants import *
26521d
 from lib389.plugins import MemberOfPlugin
26521d
-from lib389 import agreement, Entry
26521d
+from lib389 import Entry
26521d
 from lib389.idm.user import UserAccount, UserAccounts, TEST_USER_PROPERTIES
26521d
 from lib389.idm.group import Groups, Group
26521d
 from lib389.replica import ReplicationManager
26521d
@@ -49,7 +48,7 @@ def add_users(topo_m2, users_num, suffix):
26521d
     users_list = []
26521d
     users = UserAccounts(topo_m2.ms["master1"], suffix, rdn=None)
26521d
     log.info('Adding %d users' % users_num)
26521d
-    for num in sample(range(1000), users_num):
26521d
+    for num in sample(list(range(1000)), users_num):
26521d
         num_ran = int(round(num))
26521d
         USER_NAME = 'test%05d' % num_ran
26521d
         user = users.create(properties={
26521d
@@ -76,8 +75,8 @@ def config_memberof(server):
26521d
     for ent in ents:
26521d
         log.info('update %s to add nsDS5ReplicatedAttributeListTotal' % ent.dn)
26521d
         server.agreement.setProperties(agmnt_dn=ents[0].dn,
26521d
-                                       properties={RA_FRAC_EXCLUDE:'(objectclass=*) $ EXCLUDE memberOf',
26521d
-                                                   RA_FRAC_EXCLUDE_TOTAL_UPDATE:'(objectclass=*) $ EXCLUDE '})
26521d
+                                       properties={RA_FRAC_EXCLUDE: '(objectclass=*) $ EXCLUDE memberOf',
26521d
+                                                   RA_FRAC_EXCLUDE_TOTAL_UPDATE: '(objectclass=*) $ EXCLUDE '})
26521d
 
26521d
 
26521d
 def send_updates_now(server):
26521d
@@ -184,14 +183,14 @@ def test_memberof_with_repl(topo):
26521d
     for i in range(3):
26521d
         CN = '%s%d' % (GROUP_CN, i)
26521d
         groups = Groups(M1, SUFFIX)
26521d
-        testgroup = groups.create(properties={'cn' : CN})
26521d
+        testgroup = groups.create(properties={'cn': CN})
26521d
         time.sleep(2)
26521d
         test_groups.append(testgroup)
26521d
 
26521d
     # Step 4
26521d
     #Now start testing by adding differnt user to differn group
26521d
     if not ds_is_older('1.3.7'):
26521d
-           test_groups[0].remove('objectClass', 'nsMemberOf')
26521d
+        test_groups[0].remove('objectClass', 'nsMemberOf')
26521d
 
26521d
     member_dn = test_users[0].dn
26521d
     grp0_dn = test_groups[0].dn
26521d
@@ -211,7 +210,7 @@ def test_memberof_with_repl(topo):
26521d
     # Step 7
26521d
     for i in [grp0_dn, grp1_dn]:
26521d
         for inst in [M1, H1, C1]:
26521d
-             _find_memberof(inst, member_dn, i)
26521d
+            _find_memberof(inst, member_dn, i)
26521d
 
26521d
     # Step 8
26521d
     for i in [M1, H1, C1]:
26521d
@@ -225,7 +224,7 @@ def test_memberof_with_repl(topo):
26521d
     # For negative testcase, we are using assertionerror
26521d
     for inst in [M1, H1, C1]:
26521d
         for i in [grp0_dn, member_dn]:
26521d
-             with pytest.raises(AssertionError):
26521d
+            with pytest.raises(AssertionError):
26521d
                 _find_memberof(inst, i, grp1_dn)
26521d
 
26521d
     # Step 11
26521d
@@ -369,7 +368,7 @@ def test_memberof_with_changelog_reset(topo_m2):
26521d
                          'objectclass': ensure_list_bytes(['top', 'groupOfNames'])}
26521d
 
26521d
     for user in users_list:
26521d
-        dic_of_attributes.setdefault('member',[])
26521d
+        dic_of_attributes.setdefault('member', [])
26521d
         dic_of_attributes['member'].append(user.dn)
26521d
 
26521d
     log.info('Adding the test group using async function')
26521d
@@ -427,7 +426,7 @@ def rename_entry(server, cn, from_subtree, to_subtree):
26521d
     server.rename_s(dn, nrdn, newsuperior=to_subtree, delold=0)
26521d
 
26521d
 
26521d
-def _find_memberof(server, user_dn=None, group_dn=None, find_result=True):
26521d
+def _find_memberof_ext(server, user_dn=None, group_dn=None, find_result=True):
26521d
     assert (server)
26521d
     assert (user_dn)
26521d
     assert (group_dn)
26521d
@@ -495,18 +494,19 @@ def test_memberof_group(topology_st):
26521d
     dn2 = '%s,%s' % ('uid=test_m2', SUBTREE_1)
26521d
     g1 = '%s,%s' % ('cn=g1', SUBTREE_1)
26521d
     g2 = '%s,%s' % ('cn=g2', SUBTREE_2)
26521d
-    _find_memberof(inst, dn1, g1, True)
26521d
-    _find_memberof(inst, dn2, g1, True)
26521d
-    _find_memberof(inst, dn1, g2, False)
26521d
-    _find_memberof(inst, dn2, g2, False)
26521d
+    _find_memberof_ext(inst, dn1, g1, True)
26521d
+    _find_memberof_ext(inst, dn2, g1, True)
26521d
+    _find_memberof_ext(inst, dn1, g2, False)
26521d
+    _find_memberof_ext(inst, dn2, g2, False)
26521d
 
26521d
     rename_entry(inst, 'cn=g2', SUBTREE_2, SUBTREE_1)
26521d
 
26521d
     g2n = '%s,%s' % ('cn=g2-new', SUBTREE_1)
26521d
-    _find_memberof(inst, dn1, g1, True)
26521d
-    _find_memberof(inst, dn2, g1, True)
26521d
-    _find_memberof(inst, dn1, g2n, True)
26521d
-    _find_memberof(inst, dn2, g2n, True)
26521d
+    _find_memberof_ext(inst, dn1, g1, True)
26521d
+    _find_memberof_ext(inst, dn2, g1, True)
26521d
+    _find_memberof_ext(inst, dn1, g2n, True)
26521d
+    _find_memberof_ext(inst, dn2, g2n, True)
26521d
+
26521d
 
26521d
 def _config_memberof_entrycache_on_modrdn_failure(server):
26521d
 
26521d
@@ -517,11 +517,13 @@ def _config_memberof_entrycache_on_modrdn_failure(server):
26521d
                                          (ldap.MOD_REPLACE, 'memberOfEntryScope', peoplebase.encode()),
26521d
                                          (ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsMemberOf')])
26521d
 
26521d
+
26521d
 def _disable_auto_oc_memberof(server):
26521d
     MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
26521d
     server.modify_s(MEMBEROF_PLUGIN_DN,
26521d
         [(ldap.MOD_REPLACE, 'memberOfAutoAddOC', b'nsContainer')])
26521d
 
26521d
+
26521d
 @pytest.mark.ds49967
26521d
 def test_entrycache_on_modrdn_failure(topology_st):
26521d
     """This test checks that when a modrdn fails, the destination entry is not returned by a search
26521d
@@ -657,7 +659,7 @@ def test_entrycache_on_modrdn_failure(topology_st):
26521d
         topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
26521d
         topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
26521d
         assert False
26521d
-    except ldap.OPERATIONS_ERROR:
26521d
+    except ldap.OBJECT_CLASS_VIOLATION:
26521d
         pass
26521d
 
26521d
     # retrieve the entry having the specific description value
26521d
@@ -671,9 +673,11 @@ def test_entrycache_on_modrdn_failure(topology_st):
26521d
             assert ent.dn == group2_dn
26521d
     assert found
26521d
 
26521d
+
26521d
 def _config_memberof_silent_memberof_failure(server):
26521d
     _config_memberof_entrycache_on_modrdn_failure(server)
26521d
 
26521d
+
26521d
 def test_silent_memberof_failure(topology_st):
26521d
     """This test checks that if during a MODRDN, the memberof plugin fails
26521d
     then MODRDN also fails
26521d
@@ -817,7 +821,7 @@ def test_silent_memberof_failure(topology_st):
26521d
         topology_st.standalone.rename_s(group2_dn, 'cn=group_in2', newsuperior=peoplebase, delold=0)
26521d
         topology_st.standalone.log.info("This is unexpected, modrdn should fail as the member entry have not the appropriate objectclass")
26521d
         assert False
26521d
-    except ldap.OPERATIONS_ERROR:
26521d
+    except ldap.OBJECT_CLASS_VIOLATION:
26521d
         pass
26521d
 
26521d
     # Check the those entries have not memberof
26521d
@@ -843,7 +847,7 @@ def test_silent_memberof_failure(topology_st):
26521d
     except ldap.OPERATIONS_ERROR:
26521d
         pass
26521d
 
26521d
-    # Check the those entries have not memberof
26521d
+    # Check the those entries do not have memberof
26521d
     for i in (4, 5):
26521d
         user_dn = 'cn=user%d,%s' % (i, peoplebase)
26521d
         ent = topology_st.standalone.getEntry(user_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
26521d
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
26521d
index 329a845a7..b54eb3977 100644
26521d
--- a/ldap/servers/plugins/memberof/memberof.c
26521d
+++ b/ldap/servers/plugins/memberof/memberof.c
26521d
@@ -919,8 +919,7 @@ memberof_postop_modrdn(Slapi_PBlock *pb)
26521d
              * entry that is being renamed. */
26521d
             for (i = 0; configCopy.groupattrs && configCopy.groupattrs[i]; i++) {
26521d
                 if (0 == slapi_entry_attr_find(post_e, configCopy.groupattrs[i], &attr)) {
26521d
-                    if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn,
26521d
-                                                        post_sdn, attr) != 0)) {
26521d
+                    if ((ret = memberof_moddn_attr_list(pb, &configCopy, pre_sdn, post_sdn, attr)) != 0) {
26521d
                         slapi_log_err(SLAPI_LOG_ERR, MEMBEROF_PLUGIN_SUBSYSTEM,
26521d
                                       "memberof_postop_modrdn - Update failed for (%s), error (%d)\n",
26521d
                                       slapi_sdn_get_dn(pre_sdn), ret);
26521d
@@ -1720,12 +1719,32 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config, int mod_o
26521d
                 replace_mod.mod_values = replace_val;
26521d
             }
26521d
             rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
26521d
-            if (rc == LDAP_NO_SUCH_ATTRIBUTE) {
26521d
-                /* the memberof values to be replaced do not exist
26521d
-                 * just add the new values */
26521d
-                mods[0] = mods[1];
26521d
-                mods[1] = NULL;
26521d
-                rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
26521d
+            if (rc == LDAP_NO_SUCH_ATTRIBUTE || rc == LDAP_TYPE_OR_VALUE_EXISTS) {
26521d
+                if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
26521d
+                    /*
26521d
+                     * For some reason the new modrdn value is present, so retry
26521d
+                     * the delete by itself and ignore the add op by tweaking
26521d
+                     * the mod array.
26521d
+                     */
26521d
+                    mods[1] = NULL;
26521d
+                    rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
26521d
+                } else {
26521d
+                    /*
26521d
+                     * The memberof value to be replaced does not exist so just
26521d
+                     * add the new value.  Shuffle the mod array to apply only
26521d
+                     * the add operation.
26521d
+                     */
26521d
+                    mods[0] = mods[1];
26521d
+                    mods[1] = NULL;
26521d
+                    rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
26521d
+                    if (rc == LDAP_TYPE_OR_VALUE_EXISTS) {
26521d
+                        /*
26521d
+                         * The entry already has the expected memberOf value, no
26521d
+                         * problem just return success.
26521d
+                         */
26521d
+                        rc = LDAP_SUCCESS;
26521d
+                    }
26521d
+                }
26521d
             }
26521d
         }
26521d
     }
26521d
-- 
26521d
2.17.2
26521d