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

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