Blame SOURCES/0011-Ticket-49064-RFE-allow-to-enable-MemberOf-plugin-in-.patch

081b2d
From 4af03a6a2a59684950d887d42c6e9d8b027d71f5 Mon Sep 17 00:00:00 2001
081b2d
From: Thierry Bordaz <tbordaz@redhat.com>
081b2d
Date: Mon, 16 Oct 2017 11:21:51 +0200
081b2d
Subject: [PATCH] Ticket 49064 - RFE allow to enable MemberOf plugin in
081b2d
 dedicated consumer
081b2d
081b2d
Bug Description:
081b2d
	memberof triggers some internal updates to add/del 'memberof' values.
081b2d
	on a readonly consumer, those updates selects a REFERRAL_ON_UPDATE backend
081b2d
	and that is not followed by internal updates.
081b2d
	At the end of the day, the update is rejected and if memberof plugin is enabled
081b2d
	replication will stuck on that rejected update
081b2d
081b2d
Fix Description:
081b2d
	internal updates from memberof need to bypassing referrals.
081b2d
	So they flag internal updates SLAPI_OP_FLAG_BYPASS_REFERRALS, so that mtn_get_be
081b2d
	(mapping tree selection) will not return the referrals.
081b2d
081b2d
https://pagure.io/389-ds-base/issue/49064
081b2d
081b2d
Reviewed by: Ludwig Krispenz, William Brown (thanks a LOT !)
081b2d
081b2d
Platforms tested: F23 (all tickets + basic suite)
081b2d
081b2d
Flag Day: no
081b2d
081b2d
Doc impact: no
081b2d
---
081b2d
 dirsrvtests/tests/tickets/ticket49064_test.py | 259 ++++++++++++++++++++++++++
081b2d
 ldap/servers/plugins/memberof/memberof.c      |   6 +-
081b2d
 2 files changed, 262 insertions(+), 3 deletions(-)
081b2d
 create mode 100644 dirsrvtests/tests/tickets/ticket49064_test.py
081b2d
081b2d
diff --git a/dirsrvtests/tests/tickets/ticket49064_test.py b/dirsrvtests/tests/tickets/ticket49064_test.py
081b2d
new file mode 100644
081b2d
index 000000000..b4b6de4b9
081b2d
--- /dev/null
081b2d
+++ b/dirsrvtests/tests/tickets/ticket49064_test.py
081b2d
@@ -0,0 +1,259 @@
081b2d
+import logging
081b2d
+import pytest
081b2d
+import os
081b2d
+import time
081b2d
+import ldap
081b2d
+import subprocess
081b2d
+from lib389.utils import ds_is_older
081b2d
+from lib389.topologies import topology_m1h1c1 as topo
081b2d
+from lib389._constants import *
081b2d
+from lib389 import Entry
081b2d
+
081b2d
+# Skip on older versions
081b2d
+pytestmark = pytest.mark.skipif(ds_is_older('1.3.7'), reason="Not implemented")
081b2d
+
081b2d
+USER_CN='user_'
081b2d
+GROUP_CN='group_'
081b2d
+FIXUP_FILTER = '(objectClass=*)'
081b2d
+FIXUP_CMD = 'fixup-memberof.pl'
081b2d
+
081b2d
+DEBUGGING = os.getenv("DEBUGGING", default=False)
081b2d
+if DEBUGGING:
081b2d
+    logging.getLogger(__name__).setLevel(logging.DEBUG)
081b2d
+else:
081b2d
+    logging.getLogger(__name__).setLevel(logging.INFO)
081b2d
+log = logging.getLogger(__name__)
081b2d
+
081b2d
+def memberof_fixup_task(server):
081b2d
+    sbin_dir = server.get_sbin_dir()
081b2d
+    memof_task = os.path.join(sbin_dir, FIXUP_CMD)
081b2d
+    try:
081b2d
+        output = subprocess.check_output(
081b2d
+            [memof_task, '-D', DN_DM, '-w', PASSWORD, '-b', SUFFIX, '-Z', SERVERID_CONSUMER_1, '-f', FIXUP_FILTER])
081b2d
+    except subprocess.CalledProcessError as err:
081b2d
+        output = err.output
081b2d
+    log.info('output: {}'.format(output))
081b2d
+    expected = "Successfully added task entry"
081b2d
+    assert expected in output
081b2d
+
081b2d
+def config_memberof(server):
081b2d
+
081b2d
+    server.plugins.enable(name=PLUGIN_MEMBER_OF)
081b2d
+    MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
081b2d
+    server.modify_s(MEMBEROF_PLUGIN_DN, [(ldap.MOD_REPLACE,
081b2d
+                                          'memberOfAllBackends',
081b2d
+                                          'on'),
081b2d
+                                          (ldap.MOD_REPLACE, 'memberOfAutoAddOC', 'nsMemberOf')])
081b2d
+    # Configure fractional to prevent total init to send memberof
081b2d
+    ents = server.agreement.list(suffix=DEFAULT_SUFFIX)
081b2d
+    for ent in ents:
081b2d
+        log.info('update %s to add nsDS5ReplicatedAttributeListTotal' % ent.dn)
081b2d
+        server.modify_s(ent.dn,
081b2d
+                              [(ldap.MOD_REPLACE,
081b2d
+                                'nsDS5ReplicatedAttributeListTotal',
081b2d
+                                '(objectclass=*) $ EXCLUDE '),
081b2d
+                               (ldap.MOD_REPLACE,
081b2d
+                                'nsDS5ReplicatedAttributeList',
081b2d
+                                '(objectclass=*) $ EXCLUDE memberOf')])
081b2d
+
081b2d
+
081b2d
+def send_updates_now(server):
081b2d
+
081b2d
+    ents = server.agreement.list(suffix=DEFAULT_SUFFIX)
081b2d
+    for ent in ents:
081b2d
+        server.agreement.pause(ent.dn)
081b2d
+        server.agreement.resume(ent.dn)
081b2d
+                                
081b2d
+def add_user(server, no, desc='dummy', sleep=True):
081b2d
+    cn = '%s%d' % (USER_CN, no)
081b2d
+    dn = 'cn=%s,ou=people,%s' % (cn, SUFFIX)
081b2d
+    log.fatal('Adding user (%s): ' % dn)
081b2d
+    server.add_s(Entry((dn, {'objectclass': ['top', 'person', 'inetuser'],
081b2d
+                             'sn': ['_%s' % cn],
081b2d
+                             'description': [desc]})))
081b2d
+    if sleep:
081b2d
+        time.sleep(2)
081b2d
+
081b2d
+def add_group(server, nr, sleep=True):
081b2d
+    cn = '%s%d' % (GROUP_CN, nr)
081b2d
+    dn = 'cn=%s,ou=groups,%s' % (cn, SUFFIX)
081b2d
+    server.add_s(Entry((dn, {'objectclass': ['top', 'groupofnames'],
081b2d
+                             'description': 'group %d' % nr})))
081b2d
+    if sleep:
081b2d
+        time.sleep(2)
081b2d
+
081b2d
+def update_member(server, member_dn, group_dn, op, sleep=True):
081b2d
+    mod = [(op, 'member', member_dn)]
081b2d
+    server.modify_s(group_dn, mod)
081b2d
+    if sleep:
081b2d
+        time.sleep(2)
081b2d
+        
081b2d
+def _find_memberof(server, member_dn, group_dn, find_result=True):
081b2d
+    ent = server.getEntry(member_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
081b2d
+    found = False
081b2d
+    if ent.hasAttr('memberof'):
081b2d
+
081b2d
+        for val in ent.getValues('memberof'):
081b2d
+            server.log.info("!!!!!!! %s: memberof->%s" % (member_dn, val))
081b2d
+            server.log.info("!!!!!!! %s" % (val))
081b2d
+            server.log.info("!!!!!!! %s" % (group_dn))
081b2d
+            if val.lower() == group_dn.lower():
081b2d
+                found = True
081b2d
+                break
081b2d
+
081b2d
+    if find_result:
081b2d
+        assert (found)
081b2d
+    else:
081b2d
+        assert (not found)
081b2d
+
081b2d
+
081b2d
+def test_ticket49064(topo):
081b2d
+    """Specify a test case purpose or name here
081b2d
+
081b2d
+    :id: 60c11636-55a1-4704-9e09-2c6bcc828de4
081b2d
+    :setup: 1 Master - 1 Hub - 1 Consumer
081b2d
+    :steps:
081b2d
+        1. Configure replication to EXCLUDE memberof
081b2d
+        2. Enable memberof plugin
081b2d
+        3. Create users/groups
081b2d
+        4. make user_1 member of group_1
081b2d
+        5. Checks that user_1 is memberof group_1 on M,H,C
081b2d
+        6. make group_1 member of group_2 (nest group)
081b2d
+        7. Checks that user_1 is memberof group_1 and group_2 on M,H,C
081b2d
+        8. Check group_1 is memberof group_2 on M,H,C
081b2d
+        9. remove group_1 from group_2
081b2d
+        10. Check group_1 and user_1 are NOT memberof group_2 on M,H,C
081b2d
+        11. remove user_1 from group_1
081b2d
+        12. Check user_1 is NOT memberof group_1 and group_2 on M,H,C
081b2d
+        13. Disable memberof on C1
081b2d
+        14. make user_1 member of group_1
081b2d
+        15. Checks that user is memberof group_1 on M,H but not on C
081b2d
+        16. Enable memberof on C1
081b2d
+        17. Checks that user is memberof group_1 on M,H but not on C
081b2d
+        18. Run memberof fixup task
081b2d
+        19. Checks that user is memberof group_1 on M,H,C
081b2d
+
081b2d
+        
081b2d
+    :expectedresults:
081b2d
+        no assert for membership check
081b2d
+    """
081b2d
+
081b2d
+
081b2d
+    M1 = topo.ms["master1"]
081b2d
+    H1 = topo.hs["hub1"]
081b2d
+    C1 = topo.cs["consumer1"]
081b2d
+
081b2d
+    # Step 1 & 2
081b2d
+    M1.config.enable_log('audit')
081b2d
+    config_memberof(M1)
081b2d
+    M1.restart()
081b2d
+    
081b2d
+    H1.config.enable_log('audit')
081b2d
+    config_memberof(H1)
081b2d
+    H1.restart()
081b2d
+    
081b2d
+    C1.config.enable_log('audit')
081b2d
+    config_memberof(C1)
081b2d
+    C1.restart()
081b2d
+    
081b2d
+    # Step 3
081b2d
+    for i in range(10):
081b2d
+        add_user(M1, i, desc='add on m1')
081b2d
+    for i in range(3):
081b2d
+        add_group(M1, i)
081b2d
+        
081b2d
+    # Step 4
081b2d
+    member_dn = 'cn=%s%d,ou=people,%s' % (USER_CN,  1, SUFFIX)
081b2d
+    group_dn  = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 1, SUFFIX)
081b2d
+    update_member(M1, member_dn, group_dn, ldap.MOD_ADD, sleep=True)
081b2d
+    
081b2d
+    # Step 5
081b2d
+    for i in [M1, H1, C1]:
081b2d
+        _find_memberof(i, member_dn, group_dn, find_result=True)
081b2d
+ 
081b2d
+
081b2d
+    # Step 6
081b2d
+    user_dn = 'cn=%s%d,ou=people,%s' % (USER_CN,  1, SUFFIX)
081b2d
+    grp1_dn = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 1, SUFFIX)
081b2d
+    grp2_dn = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 2, SUFFIX)
081b2d
+    update_member(M1, grp1_dn, grp2_dn, ldap.MOD_ADD, sleep=True)
081b2d
+    
081b2d
+    # Step 7
081b2d
+    for i in [grp1_dn, grp2_dn]:
081b2d
+        for inst in [M1, H1, C1]:
081b2d
+            _find_memberof(inst, user_dn, i, find_result=True)
081b2d
+
081b2d
+    # Step 8
081b2d
+    for i in [M1, H1, C1]:
081b2d
+        _find_memberof(i, grp1_dn, grp2_dn, find_result=True)
081b2d
+        
081b2d
+    # Step 9
081b2d
+    user_dn = 'cn=%s%d,ou=people,%s' % (USER_CN,  1, SUFFIX)
081b2d
+    grp1_dn = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 1, SUFFIX)
081b2d
+    grp2_dn = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 2, SUFFIX)
081b2d
+    update_member(M1, grp1_dn, grp2_dn, ldap.MOD_DELETE, sleep=True)
081b2d
+
081b2d
+    # Step 10
081b2d
+    for inst in [M1, H1, C1]:
081b2d
+        for i in [grp1_dn, user_dn]:
081b2d
+            _find_memberof(inst, i, grp2_dn, find_result=False)
081b2d
+    
081b2d
+    # Step 11
081b2d
+    member_dn = 'cn=%s%d,ou=people,%s' % (USER_CN,  1, SUFFIX)
081b2d
+    group_dn  = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 1, SUFFIX)
081b2d
+    update_member(M1, member_dn, group_dn, ldap.MOD_DELETE, sleep=True)
081b2d
+    
081b2d
+    # Step 12
081b2d
+    for inst in [M1, H1, C1]:
081b2d
+        for grp in [grp1_dn, grp2_dn]:
081b2d
+            _find_memberof(inst, member_dn, grp, find_result=False)
081b2d
+    
081b2d
+    # Step 13
081b2d
+    C1.plugins.disable(name=PLUGIN_MEMBER_OF)
081b2d
+    C1.restart()
081b2d
+    
081b2d
+    # Step 14
081b2d
+    member_dn = 'cn=%s%d,ou=people,%s' % (USER_CN,  1, SUFFIX)
081b2d
+    group_dn  = 'cn=%s%d,ou=groups,%s' % (GROUP_CN, 1, SUFFIX)
081b2d
+    update_member(M1, member_dn, group_dn, ldap.MOD_ADD, sleep=True)
081b2d
+    
081b2d
+    # Step 15
081b2d
+    for i in [M1, H1]:
081b2d
+        _find_memberof(i, member_dn, group_dn, find_result=True)
081b2d
+    _find_memberof(C1, member_dn, group_dn, find_result=False)
081b2d
+    
081b2d
+    # Step 16
081b2d
+    C1.plugins.enable(name=PLUGIN_MEMBER_OF)
081b2d
+    C1.restart()
081b2d
+    
081b2d
+    # Step 17
081b2d
+    for i in [M1, H1]:
081b2d
+        _find_memberof(i, member_dn, group_dn, find_result=True)
081b2d
+    _find_memberof(C1, member_dn, group_dn, find_result=False)
081b2d
+    
081b2d
+    # Step 18
081b2d
+    memberof_fixup_task(C1)
081b2d
+    time.sleep(5)
081b2d
+
081b2d
+    # Step 19
081b2d
+    for i in [M1, H1, C1]:
081b2d
+        _find_memberof(i, member_dn, group_dn, find_result=True)
081b2d
+        
081b2d
+    # If you need any test suite initialization,
081b2d
+    # please, write additional fixture for that (including finalizer).
081b2d
+    # Topology for suites are predefined in lib389/topologies.py.
081b2d
+
081b2d
+    # If you need host, port or any other data about instance,
081b2d
+    # Please, use the instance object attributes for that (for example, topo.ms["master1"].serverid)
081b2d
+
081b2d
+    if DEBUGGING:
081b2d
+        # Add debugging steps(if any)...
081b2d
+        pass
081b2d
+
081b2d
+
081b2d
+if __name__ == '__main__':
081b2d
+    # Run isolated
081b2d
+    # -s for DEBUG mode
081b2d
+    CURRENT_FILE = os.path.realpath(__file__)
081b2d
+    pytest.main("-s %s" % CURRENT_FILE)
081b2d
+
081b2d
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
081b2d
index bae242c81..44b52edbb 100644
081b2d
--- a/ldap/servers/plugins/memberof/memberof.c
081b2d
+++ b/ldap/servers/plugins/memberof/memberof.c
081b2d
@@ -609,7 +609,7 @@ memberof_del_dn_type_callback(Slapi_Entry *e, void *callback_data)
081b2d
     slapi_modify_internal_set_pb_ext(
081b2d
         mod_pb, slapi_entry_get_sdn(e),
081b2d
         mods, 0, 0,
081b2d
-        memberof_get_plugin_id(), 0);
081b2d
+        memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
081b2d
 
081b2d
     slapi_modify_internal_pb(mod_pb);
081b2d
 
081b2d
@@ -3224,7 +3224,7 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
081b2d
         mod_pb = slapi_pblock_new();
081b2d
         slapi_modify_internal_set_pb(
081b2d
             mod_pb, dn, mods, 0, 0,
081b2d
-            memberof_get_plugin_id(), 0);
081b2d
+            memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
081b2d
         slapi_modify_internal_pb(mod_pb);
081b2d
 
081b2d
         slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
081b2d
@@ -3279,7 +3279,7 @@ memberof_add_objectclass(char *auto_add_oc, const char *dn)
081b2d
 
081b2d
     slapi_modify_internal_set_pb(
081b2d
         mod_pb, dn, mods, 0, 0,
081b2d
-        memberof_get_plugin_id(), 0);
081b2d
+        memberof_get_plugin_id(), SLAPI_OP_FLAG_BYPASS_REFERRALS);
081b2d
     slapi_modify_internal_pb(mod_pb);
081b2d
 
081b2d
     slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
081b2d
-- 
081b2d
2.13.6
081b2d