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

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