Blame SOURCES/0055-Ticket-49523-memberof-schema-violation-error-message.patch

058656
From 60198729ba59f673aae2ae1db1d9668b674ad429 Mon Sep 17 00:00:00 2001
058656
From: Thierry Bordaz <tbordaz@redhat.com>
058656
Date: Fri, 5 Jan 2018 15:31:44 +0100
058656
Subject: [PATCH] Ticket 49523 - memberof: schema violation error message is
058656
 confusing as memberof will likely repair target entry
058656
058656
Bug Description:
058656
	When memberof is enabled it adds 'memberof' attribute to members entries.
058656
	If a member entry has not the appropriate objectclass to support 'memberof' attribute an ERR is logged.
058656
058656
	ERR - oc_check_allowed_sv - Entry "cn=user_1,ou=People,dc=example,dc=com" -- attribute "memberOf" not allowed
058656
058656
	This is confusing because memberof will catch this violation and may try to repair it.
058656
	So although this message is alarming, the target entry may finally have the 'memberof' attribute.
058656
058656
	This is especially confusing since https://pagure.io/389-ds-base/issue/48985 where the repair operation
058656
	is done by default (if schema is violated)
058656
058656
	We can not (and should not) eliminate the schema violation message.
058656
	But memberof should log a additional warning (beside the schema violation msg) stating it repaired the violation.
058656
058656
Fix Description:
058656
058656
	Add a warning message upon repair operation
058656
		ERR - oc_check_allowed_sv - Entry "<entry_dn>" -- attribute "memberOf" not allowed
058656
		WARN - memberof-plugin - Entry <entry_dn> - schema violation caught - repair operation succeeded
058656
058656
https://pagure.io/389-ds-base/issue/49523
058656
058656
Reviewed by: Mark Reynolds
058656
058656
Platforms tested: F26
058656
058656
Flag Day: no
058656
058656
Doc impact: no
058656
---
058656
 dirsrvtests/tests/tickets/ticket49523_test.py | 154 ++++++++++++++++++++++++++
058656
 ldap/servers/plugins/memberof/memberof.c      |   8 +-
058656
 2 files changed, 161 insertions(+), 1 deletion(-)
058656
 create mode 100644 dirsrvtests/tests/tickets/ticket49523_test.py
058656
058656
diff --git a/dirsrvtests/tests/tickets/ticket49523_test.py b/dirsrvtests/tests/tickets/ticket49523_test.py
058656
new file mode 100644
058656
index 000000000..c3296ef07
058656
--- /dev/null
058656
+++ b/dirsrvtests/tests/tickets/ticket49523_test.py
058656
@@ -0,0 +1,154 @@
058656
+import logging
058656
+import pytest
058656
+import os
058656
+import ldap
058656
+import time
058656
+import re
058656
+from lib389.plugins import MemberOfPlugin
058656
+from lib389._constants import *
058656
+from lib389.topologies import topology_st as topo
058656
+from lib389 import Entry
058656
+
058656
+DEBUGGING = os.getenv("DEBUGGING", default=False)
058656
+if DEBUGGING:
058656
+    logging.getLogger(__name__).setLevel(logging.DEBUG)
058656
+else:
058656
+    logging.getLogger(__name__).setLevel(logging.INFO)
058656
+log = logging.getLogger(__name__)
058656
+
058656
+
058656
+USER_CN='user_'
058656
+GROUP_CN='group_'
058656
+def _user_get_dn(no):
058656
+    cn = '%s%d' % (USER_CN, no)
058656
+    dn = 'cn=%s,ou=people,%s' % (cn, SUFFIX)
058656
+    return (cn, dn)
058656
+
058656
+def add_user(server, no, desc='dummy', sleep=True):
058656
+    (cn, dn) = _user_get_dn(no)
058656
+    log.fatal('Adding user (%s): ' % dn)
058656
+    server.add_s(Entry((dn, {'objectclass': ['top', 'person'],
058656
+                             'cn': [cn],
058656
+                             'description': [desc],
058656
+                             'sn': [cn],
058656
+                             'description': ['add on that host']})))
058656
+    if sleep:
058656
+        time.sleep(2)
058656
+        
058656
+def add_group(server, nr, sleep=True):
058656
+    cn = '%s%d' % (GROUP_CN, nr)
058656
+    dn = 'cn=%s,ou=groups,%s' % (cn, SUFFIX)
058656
+    server.add_s(Entry((dn, {'objectclass': ['top', 'groupofnames'],
058656
+                             'description': 'group %d' % nr})))
058656
+    if sleep:
058656
+        time.sleep(2)
058656
+
058656
+def update_member(server, member_dn, group_dn, op, sleep=True):
058656
+    mod = [(op, 'member', member_dn)]
058656
+    server.modify_s(group_dn, mod)
058656
+    if sleep:
058656
+        time.sleep(2)
058656
+        
058656
+def _find_memberof(server, member_dn, group_dn, find_result=True):
058656
+    ent = server.getEntry(member_dn, ldap.SCOPE_BASE, "(objectclass=*)", ['memberof'])
058656
+    found = False
058656
+    if ent.hasAttr('memberof'):
058656
+
058656
+        for val in ent.getValues('memberof'):
058656
+            server.log.info("!!!!!!! %s: memberof->%s" % (member_dn, val))
058656
+            server.log.info("!!!!!!! %s" % (val))
058656
+            server.log.info("!!!!!!! %s" % (group_dn))
058656
+            if val.lower() == group_dn.lower():
058656
+                found = True
058656
+                break
058656
+
058656
+    if find_result:
058656
+        assert (found)
058656
+    else:
058656
+        assert (not found)
058656
+        
058656
+def pattern_accesslog(server, log_pattern):
058656
+    file_obj = open(server.accesslog, "r")
058656
+
058656
+    found = False
058656
+    # Use a while true iteration because 'for line in file: hit a
058656
+    while True:
058656
+        line = file_obj.readline()
058656
+        found = log_pattern.search(line)
058656
+        if ((line == '') or (found)):
058656
+            break
058656
+
058656
+    return found
058656
+
058656
+def pattern_errorlog(server, log_pattern):
058656
+    file_obj = open(server.errlog, "r")
058656
+
058656
+    found = None
058656
+    # Use a while true iteration because 'for line in file: hit a
058656
+    while True:
058656
+        line = file_obj.readline()
058656
+        found = log_pattern.search(line)
058656
+        server.log.fatal("%s --> %s" % (line, found))
058656
+        if ((line == '') or (found)):
058656
+            break
058656
+
058656
+    return found
058656
+        
058656
+def test_ticket49523(topo):
058656
+    """Specify a test case purpose or name here
058656
+
058656
+    :id: e2af0aaa-447e-4e85-a5ce-57ae66260d0b
058656
+    :setup: Fill in set up configuration here
058656
+    :steps:
058656
+        1. Fill in test case steps here
058656
+        2. And indent them like this (RST format requirement)
058656
+    :expectedresults:
058656
+        1. Fill in the result that is expected
058656
+        2. For each test step
058656
+    """
058656
+
058656
+    # If you need any test suite initialization,
058656
+    # please, write additional fixture for that (including finalizer).
058656
+    # Topology for suites are predefined in lib389/topologies.py.
058656
+
058656
+    # If you need host, port or any other data about instance,
058656
+    # Please, use the instance object attributes for that (for example, topo.ms["master1"].serverid)
058656
+    inst = topo.standalone
058656
+    memberof = MemberOfPlugin(inst)
058656
+    memberof.enable()
058656
+    memberof.set_autoaddoc('nsMemberOf')
058656
+    inst.restart()
058656
+    
058656
+    # Step 2
058656
+    for i in range(10):
058656
+        add_user(inst, i, desc='add user')
058656
+    
058656
+    add_group(inst, 1)
058656
+    
058656
+    group_parent_dn = 'ou=groups,%s' % (SUFFIX)
058656
+    group_rdn = 'cn=%s%d' % (GROUP_CN, 1)
058656
+    group_dn  = '%s,%s' % (group_rdn, group_parent_dn)
058656
+    (member_cn, member_dn) = _user_get_dn(1)
058656
+    update_member(inst, member_dn, group_dn, ldap.MOD_ADD, sleep=False)
058656
+    
058656
+    _find_memberof(inst, member_dn, group_dn, find_result=True)
058656
+    
058656
+    pattern = ".*oc_check_allowed_sv - Entry.*cn=%s.* -- attribute.*not allowed.*" % member_cn
058656
+    log.fatal("pattern = %s" % pattern)
058656
+    regex = re.compile(pattern)
058656
+    assert pattern_errorlog(inst, regex)
058656
+    
058656
+    regex = re.compile(".*schema violation caught - repair operation.*")
058656
+    assert pattern_errorlog(inst, regex)
058656
+
058656
+    if DEBUGGING:
058656
+        # Add debugging steps(if any)...
058656
+        pass
058656
+
058656
+
058656
+if __name__ == '__main__':
058656
+    # Run isolated
058656
+    # -s for DEBUG mode
058656
+    CURRENT_FILE = os.path.realpath(__file__)
058656
+    pytest.main("-s %s" % CURRENT_FILE)
058656
+
058656
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
058656
index 44b52edbb..fcfa7817d 100644
058656
--- a/ldap/servers/plugins/memberof/memberof.c
058656
+++ b/ldap/servers/plugins/memberof/memberof.c
058656
@@ -3236,8 +3236,14 @@ memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
058656
                  */
058656
                 break;
058656
             }
058656
-            if (memberof_add_objectclass(add_oc, dn)) {
058656
+            rc = memberof_add_objectclass(add_oc, dn);
058656
+            slapi_log_err(SLAPI_LOG_WARNING, MEMBEROF_PLUGIN_SUBSYSTEM,
058656
+                    "Entry %s - schema violation caught - repair operation %s\n",
058656
+                    dn ? dn : "unknown",
058656
+                    rc ? "failed" : "succeeded");
058656
+            if (rc) {
058656
                 /* Failed to add objectclass */
058656
+                rc = LDAP_OBJECT_CLASS_VIOLATION;
058656
                 break;
058656
             }
058656
             added_oc = 1;
058656
-- 
058656
2.13.6
058656