From 114949d89e4a40cd3791a39065c0faa827b39f49 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Wed, 2 Sep 2015 11:51:35 -0400
Subject: [PATCH] Ticket 48267 - Add config setting to MO plugin to add
 objectclass

Description:  Add setting to plugin to auto add a predefined
              objectclass that allows "memberOf" attribute

https://fedorahosted.org/389/ticket/48267

Reviewed by: ?
---
 .../suites/memberof_plugin/memberof_test.py        | 149 ++++++++++++++++-----
 ldap/servers/plugins/memberof/memberof.c           | 145 ++++++++++++++------
 ldap/servers/plugins/memberof/memberof.h           |   2 +
 ldap/servers/plugins/memberof/memberof_config.c    |  27 +++-
 4 files changed, 245 insertions(+), 78 deletions(-)

diff --git a/dirsrvtests/suites/memberof_plugin/memberof_test.py b/dirsrvtests/suites/memberof_plugin/memberof_test.py
index b3c98f7..e97c09a 100644
--- a/dirsrvtests/suites/memberof_plugin/memberof_test.py
+++ b/dirsrvtests/suites/memberof_plugin/memberof_test.py
@@ -3,9 +3,10 @@
 # All rights reserved.
 #
 # License: GPL (version 3 or any later version).
-# See LICENSE for details. 
+# See LICENSE for details.
 # --- END COPYRIGHT BLOCK ---
 #
+
 import os
 import sys
 import time
@@ -21,9 +22,13 @@ from lib389.utils import *
 
 logging.getLogger(__name__).setLevel(logging.DEBUG)
 log = logging.getLogger(__name__)
-
 installation1_prefix = None
 
+MEMBEROF_PLUGIN_DN = ('cn=' + PLUGIN_MEMBER_OF + ',cn=plugins,cn=config')
+USER1_DN = 'uid=user1,' + DEFAULT_SUFFIX
+USER2_DN = 'uid=user2,' + DEFAULT_SUFFIX
+GROUP_DN = 'cn=group,' + DEFAULT_SUFFIX
+
 
 class TopologyStandalone(object):
     def __init__(self, standalone):
@@ -51,43 +56,121 @@ def topology(request):
     standalone.create()
     standalone.open()
 
+    # Delete each instance in the end
+    def fin():
+        standalone.delete()
+        #pass
+    request.addfinalizer(fin)
+
     # Clear out the tmp dir
     standalone.clearTmpDir(__file__)
 
     return TopologyStandalone(standalone)
 
 
-def test_memberof_init(topology):
-    '''
-    Write any test suite initialization here(if needed)
-    '''
-
-    return
-
-
-def test_memberof_(topology):
-    '''
-    Write a single test here...
-    '''
-
-    return
-
-
-def test_memberof_final(topology):
-    topology.standalone.delete()
-    log.info('memberof test suite PASSED')
-
-
-def run_isolated():
-    global installation1_prefix
-    installation1_prefix = None
-
-    topo = topology(True)
-    test_memberof_init(topo)
-    test_memberof_(topo)
-    test_memberof_final(topo)
+def test_memberof_auto_add_oc(topology):
+    """
+    Test the auto add objectclass feature.  The plugin should add a predefined
+    objectclass that will allow memberOf to be added to an entry.
+    """
+
+    # enable dynamic plugins
+    try:
+        topology.standalone.modify_s(DN_CONFIG,
+                                     [(ldap.MOD_REPLACE,
+                                       'nsslapd-dynamic-plugins',
+                                       'on')])
+    except ldap.LDAPError as e:
+        ldap.error('Failed to enable dynamic plugins! ' + e.message['desc'])
+        assert False
+
+    # Enable the plugin
+    topology.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
+
+    # First test invalid value (config validation)
+    topology.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
+    try:
+        topology.standalone.modify_s(MEMBEROF_PLUGIN_DN,
+                                     [(ldap.MOD_REPLACE,
+                                       'memberofAutoAddOC',
+                                       'invalid123')])
+        log.fatal('Incorrectly added invalid objectclass!')
+        assert False
+    except ldap.UNWILLING_TO_PERFORM:
+        log.info('Correctly rejected invalid objectclass')
+    except ldap.LDAPError as e:
+        ldap.error('Unexpected error adding invalid objectclass - error: ' + e.message['desc'])
+        assert False
+
+    # Add valid objectclass
+    topology.standalone.plugins.enable(name=PLUGIN_MEMBER_OF)
+    try:
+        topology.standalone.modify_s(MEMBEROF_PLUGIN_DN,
+                                     [(ldap.MOD_REPLACE,
+                                       'memberofAutoAddOC',
+                                       'inetuser')])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to configure memberOf plugin: error ' + e.message['desc'])
+        assert False
+
+    # Add two users
+    try:
+        topology.standalone.add_s(Entry((USER1_DN,
+                                         {'objectclass': 'top',
+                                          'objectclass': 'person',
+                                          'objectclass': 'organizationalPerson',
+                                          'objectclass': 'inetorgperson',
+                                          'sn': 'last',
+                                          'cn': 'full',
+                                          'givenname': 'user1',
+                                          'uid': 'user1'
+                                         })))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add user1 entry, error: ' + e.message['desc'])
+        assert False
+
+    try:
+        topology.standalone.add_s(Entry((USER2_DN,
+                                         {'objectclass': 'top',
+                                          'objectclass': 'person',
+                                          'objectclass': 'organizationalPerson',
+                                          'objectclass': 'inetorgperson',
+                                          'sn': 'last',
+                                          'cn': 'full',
+                                          'givenname': 'user2',
+                                          'uid': 'user2'
+                                         })))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add user2 entry, error: ' + e.message['desc'])
+        assert False
+
+    # Add a group(that already includes one user
+    try:
+        topology.standalone.add_s(Entry((GROUP_DN,
+                                         {'objectclass': 'top',
+                                          'objectclass': 'groupOfNames',
+                                          'cn': 'group',
+                                          'member': USER1_DN
+                                         })))
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add group entry, error: ' + e.message['desc'])
+        assert False
+
+    # Add a user to the group
+    try:
+        topology.standalone.modify_s(GROUP_DN,
+                                     [(ldap.MOD_ADD,
+                                       'member',
+                                       USER2_DN)])
+    except ldap.LDAPError as e:
+        log.fatal('Failed to add user2 to group: error ' + e.message['desc'])
+        assert False
+
+    log.info('Test complete.')
 
 
 if __name__ == '__main__':
-    run_isolated()
-
+    # Run isolated
+    # -s for DEBUG mode
+    CURRENT_FILE = os.path.realpath(__file__)
+    pytest.main("-s %s" % CURRENT_FILE)
\ No newline at end of file
diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
index 9b577b9..aad300f 100644
--- a/ldap/servers/plugins/memberof/memberof.c
+++ b/ldap/servers/plugins/memberof/memberof.c
@@ -145,6 +145,8 @@ static void memberof_fixup_task_thread(void *arg);
 static int memberof_fix_memberof(MemberOfConfig *config, char *dn, char *filter_str);
 static int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data);
 static int memberof_entry_in_scope(MemberOfConfig *config, Slapi_DN *sdn);
+static int memberof_add_objectclass(char *auto_add_oc, const char *dn);
+static int memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc);
 
 /*** implementation ***/
 
@@ -490,7 +492,7 @@ int memberof_postop_del(Slapi_PBlock *pb)
 {
 	int ret = SLAPI_PLUGIN_SUCCESS;
 	MemberOfConfig *mainConfig = NULL;
-	MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+	MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 	Slapi_DN *sdn;
 	void *caller_id = NULL;
 
@@ -818,7 +820,7 @@ int memberof_postop_modrdn(Slapi_PBlock *pb)
 	if(memberof_oktodo(pb))
 	{
 		MemberOfConfig *mainConfig = 0;
-		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 		struct slapi_entry *pre_e = NULL;
 		struct slapi_entry *post_e = NULL;
 		Slapi_DN *pre_sdn = 0;
@@ -937,6 +939,7 @@ typedef struct _replace_dn_data
 	char *pre_dn;
 	char *post_dn;
 	char *type;
+	char *add_oc;
 } replace_dn_data;
 
 
@@ -957,7 +960,9 @@ memberof_replace_dn_from_groups(Slapi_PBlock *pb, MemberOfConfig *config,
 	{
 		replace_dn_data data = {(char *)slapi_sdn_get_dn(pre_sdn),
 		                        (char *)slapi_sdn_get_dn(post_sdn),
-		                        config->groupattrs[i]};
+		                        config->groupattrs[i],
+		                        config->auto_add_oc
+		};
 
 		groupattrs[0] = config->groupattrs[i];
 
@@ -981,9 +986,9 @@ int memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
 	LDAPMod *mods[3];
 	char *delval[2];
 	char *addval[2];
-	Slapi_PBlock *mod_pb = 0;
+	char *dn = NULL;
 
-	mod_pb = slapi_pblock_new();
+	dn = slapi_entry_get_dn(e);
 
 	mods[0] = &delmod;
 	mods[1] = &addmod;
@@ -1003,18 +1008,8 @@ int memberof_replace_dn_type_callback(Slapi_Entry *e, void *callback_data)
 	addmod.mod_type = ((replace_dn_data *)callback_data)->type;
 	addmod.mod_values = addval;
 
-	slapi_modify_internal_set_pb_ext(
-		mod_pb, slapi_entry_get_sdn(e),
-		mods, 0, 0,
-		memberof_get_plugin_id(), 0);
-
-	slapi_modify_internal_pb(mod_pb);
-
-	slapi_pblock_get(mod_pb,
-		SLAPI_PLUGIN_INTOP_RESULT,
-		&rc);
-
-	slapi_pblock_destroy(mod_pb);
+	rc = memberof_add_memberof_attr(mods, dn,
+		((replace_dn_data *)callback_data)->add_oc);
 
 	return rc;
 }
@@ -1083,7 +1078,7 @@ int memberof_postop_modify(Slapi_PBlock *pb)
 	{
 		int config_copied = 0;
 		MemberOfConfig *mainConfig = 0;
-		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 
 		/* get the mod set */
 		slapi_pblock_get(pb, SLAPI_MODIFY_MODS, &mods);
@@ -1261,7 +1256,7 @@ int memberof_postop_add(Slapi_PBlock *pb)
 	if(memberof_oktodo(pb) && (sdn = memberof_getsdn(pb)))
 	{
 		struct slapi_entry *e = NULL;
-		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+		MemberOfConfig configCopy = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
 		MemberOfConfig *mainConfig;
 		slapi_pblock_get( pb, SLAPI_ENTRY_POST_OP, &e );
 
@@ -1455,7 +1450,6 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config,
 	LDAPMod *mods[3];
 	char *val[2];
 	char *replace_val[2];
-	Slapi_PBlock *mod_pb = 0;
 	Slapi_Entry *e = 0; 
 	memberofstringll *ll = 0;
 	char *op_str = 0;
@@ -1696,8 +1690,6 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config,
 			rc = memberof_fix_memberof_callback(e, config);
 		} else {
 			/* single entry - do mod */
-			mod_pb = slapi_pblock_new();
-
 			mods[0] = &mod;
 			if(LDAP_MOD_REPLACE == mod_op)
 			{
@@ -1724,19 +1716,7 @@ memberof_modop_one_replace_r(Slapi_PBlock *pb, MemberOfConfig *config,
 				replace_mod.mod_type = config->memberof_attr;
 				replace_mod.mod_values = replace_val;
 			}
-
-			slapi_modify_internal_set_pb(
-				mod_pb, op_to,
-				mods, 0, 0,
-				memberof_get_plugin_id(), 0);
-
-			slapi_modify_internal_pb(mod_pb);
-
-			slapi_pblock_get(mod_pb,
-				SLAPI_PLUGIN_INTOP_RESULT,
-				&rc);
-
-			slapi_pblock_destroy(mod_pb);
+			rc = memberof_add_memberof_attr(mods, op_to, config->auto_add_oc);
 		}
 	}
 
@@ -2899,7 +2879,6 @@ int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
 	 * with the found values.  */
 	if (groups && slapi_valueset_count(groups))
 	{
-		Slapi_PBlock *mod_pb = slapi_pblock_new();
 		Slapi_Value *val = 0;
 		Slapi_Mod *smod;
 		LDAPMod **mods = (LDAPMod **) slapi_ch_malloc(2 * sizeof(LDAPMod *));
@@ -2922,17 +2901,10 @@ int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
 		mods[0] = slapi_mod_get_ldapmod_passout(smod);
 		mods[1] = 0;
 
-		slapi_modify_internal_set_pb_ext(
-			mod_pb, sdn, mods, 0, 0,
-			memberof_get_plugin_id(), 0);
-
-		slapi_modify_internal_pb(mod_pb);
-
-		slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+		rc = memberof_add_memberof_attr(mods, slapi_sdn_get_dn(sdn), config->auto_add_oc);
 
 		ldap_mods_free(mods, 1);
 		slapi_mod_free(&smod);
-		slapi_pblock_destroy(mod_pb);
 	} else { 
 		/* No groups were found, so remove the memberOf attribute
 		 * from this entry. */
@@ -2943,3 +2915,88 @@ int memberof_fix_memberof_callback(Slapi_Entry *e, void *callback_data)
 bail:
 	return rc;
 }
+
+/*
+ * Add the "memberof" attribute to the entry.  If we get an objectclass violation,
+ * check if we are auto adding an objectclass.  IF so, add the oc, and try the
+ * operation one more time.
+ */
+static int
+memberof_add_memberof_attr(LDAPMod **mods, const char *dn, char *add_oc)
+{
+	Slapi_PBlock *mod_pb = NULL;
+	int added_oc = 0;
+	int rc = 0;
+
+	while(1){
+		mod_pb = slapi_pblock_new();
+		slapi_modify_internal_set_pb(
+			mod_pb, dn, mods, 0, 0,
+			memberof_get_plugin_id(), 0);
+		slapi_modify_internal_pb(mod_pb);
+
+		slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+		if (rc == LDAP_OBJECT_CLASS_VIOLATION){
+			if (!add_oc || added_oc){
+				/*
+				 * We aren't auto adding an objectclass, or we already
+				 * added the objectclass, and we are still failing.
+				 */
+				break;
+			}
+			if(memberof_add_objectclass(add_oc, dn)){
+				/* Failed to add objectclass */
+				break;
+			}
+			added_oc = 1;
+			slapi_pblock_destroy(mod_pb);
+		} else if (rc){
+			/* Some other fatal error */
+			break;
+		} else {
+			/* success */
+			break;
+		}
+	}
+	slapi_pblock_destroy(mod_pb);
+
+	return rc;
+}
+
+/*
+ * Add the "auto add" objectclass to an entry
+ */
+static int
+memberof_add_objectclass(char *auto_add_oc, const char *dn)
+{
+	Slapi_PBlock *mod_pb = NULL;
+	LDAPMod mod;
+	LDAPMod *mods[2];
+	char *val[2];
+	int rc = 0;
+
+	mod_pb = slapi_pblock_new();
+	mods[0] = &mod;
+	mods[1] = 0;
+	val[0] = auto_add_oc;
+	val[1] = 0;
+
+	mod.mod_op = LDAP_MOD_ADD;
+	mod.mod_type = "objectclass";
+	mod.mod_values = val;
+
+	slapi_modify_internal_set_pb(
+		mod_pb, dn, mods, 0, 0,
+		memberof_get_plugin_id(), 0);
+	slapi_modify_internal_pb(mod_pb);
+
+	slapi_pblock_get(mod_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+	if (rc){
+		slapi_log_error( SLAPI_LOG_FATAL, MEMBEROF_PLUGIN_SUBSYSTEM,
+			"Failed to add objectclass (%s) to entry (%s)\n",
+			auto_add_oc, dn);
+	}
+	slapi_pblock_destroy(mod_pb);
+
+	return rc;
+}
diff --git a/ldap/servers/plugins/memberof/memberof.h b/ldap/servers/plugins/memberof/memberof.h
index 9d9d158..ece18e4 100644
--- a/ldap/servers/plugins/memberof/memberof.h
+++ b/ldap/servers/plugins/memberof/memberof.h
@@ -41,6 +41,7 @@
 #define MEMBEROF_ENTRY_SCOPE_ATTR "memberOfEntryScope"
 #define MEMBEROF_ENTRY_SCOPE_EXCLUDE_SUBTREE "memberOfEntryScopeExcludeSubtree"
 #define MEMBEROF_SKIP_NESTED_ATTR "memberOfSkipNested"
+#define MEMBEROF_AUTO_ADD_OC "memberOfAutoAddOC"
 #define DN_SYNTAX_OID "1.3.6.1.4.1.1466.115.121.1.12"
 #define NAME_OPT_UID_SYNTAX_OID "1.3.6.1.4.1.1466.115.121.1.34"
 
@@ -60,6 +61,7 @@ typedef struct memberofconfig {
 	Slapi_Attr **group_slapiattrs;
 	int skip_nested;
 	int fixup_task;
+	char *auto_add_oc;
 } MemberOfConfig;
 
 
diff --git a/ldap/servers/plugins/memberof/memberof_config.c b/ldap/servers/plugins/memberof/memberof_config.c
index 10cbd7a..99132d8 100644
--- a/ldap/servers/plugins/memberof/memberof_config.c
+++ b/ldap/servers/plugins/memberof/memberof_config.c
@@ -180,6 +180,7 @@ memberof_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entr
 	char *syntaxoid = NULL;
 	char *config_dn = NULL;
 	char *skip_nested = NULL;
+	char *auto_add_oc = NULL;
 	char **entry_scopes = NULL;
 	char **entry_exclude_scopes = NULL;
 	int not_dn_syntax = 0;
@@ -272,6 +273,22 @@ memberof_validate_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entr
 		}
 	}
 
+	if ((auto_add_oc = slapi_entry_attr_get_charptr(e, MEMBEROF_AUTO_ADD_OC))){
+		char *sup = NULL;
+
+		/* Check if the objectclass exists by looking for its superior oc */
+		if((sup = slapi_schema_get_superior_name(auto_add_oc)) == NULL){
+			PR_snprintf(returntext, SLAPI_DSE_RETURNTEXT_SIZE,
+				"The %s configuration attribute must be set to "
+				"to an existing objectclass  (unknown: %s)",
+				MEMBEROF_AUTO_ADD_OC, auto_add_oc);
+			*returncode = LDAP_UNWILLING_TO_PERFORM;
+			goto done;
+		} else {
+			slapi_ch_free_string(&sup);
+		}
+	}
+
 	if ((config_dn = slapi_entry_attr_get_charptr(e, SLAPI_PLUGIN_SHARED_CONFIG_AREA))){
 		/* Now check the shared config attribute, validate it now */
 
@@ -412,6 +429,7 @@ done:
 	slapi_sdn_free(&config_sdn);
 	slapi_ch_free_string(&config_dn);
 	slapi_ch_free_string(&skip_nested);
+	slapi_ch_free_string(&auto_add_oc);
 
 	if (*returncode != LDAP_SUCCESS)
 	{
@@ -445,6 +463,7 @@ memberof_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry*
 	char **entryScopeExcludeSubtrees = NULL;
 	char *sharedcfg = NULL;
 	char *skip_nested = NULL;
+	char *auto_add_oc = NULL;
 	int num_vals = 0;
 
 	*returncode = LDAP_SUCCESS;
@@ -478,6 +497,7 @@ memberof_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry*
 	memberof_attr = slapi_entry_attr_get_charptr(e, MEMBEROF_ATTR);
 	allBackends = slapi_entry_attr_get_charptr(e, MEMBEROF_BACKEND_ATTR);
 	skip_nested = slapi_entry_attr_get_charptr(e, MEMBEROF_SKIP_NESTED_ATTR);
+	auto_add_oc = slapi_entry_attr_get_charptr(e, MEMBEROF_AUTO_ADD_OC);
 
 	/*
 	 * We want to be sure we don't change the config in the middle of
@@ -602,6 +622,8 @@ memberof_apply_config (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry*
 		theConfig.allBackends = 0;
 	}
 
+	theConfig.auto_add_oc = auto_add_oc;
+
 	/*
 	 * Check and process the entry scopes
 	 */
@@ -731,6 +753,9 @@ memberof_copy_config(MemberOfConfig *dest, MemberOfConfig *src)
 			dest->allBackends = src->allBackends;
 		}
 
+		slapi_ch_free_string(&dest->auto_add_oc);
+		dest->auto_add_oc = slapi_ch_strdup(src->auto_add_oc);
+
 		if(src->entryScopes){
 			int num_vals = 0;
 
@@ -770,7 +795,7 @@ memberof_free_config(MemberOfConfig *config)
 			slapi_attr_free(&config->group_slapiattrs[i]);
 		}
 		slapi_ch_free((void **)&config->group_slapiattrs);
-
+		slapi_ch_free_string(&config->auto_add_oc);
 		slapi_ch_free_string(&config->memberof_attr);
 		memberof_free_scope(config->entryScopes, &config->entryScopeCount);
 		memberof_free_scope(config->entryScopeExcludeSubtrees, &config->entryExcludeScopeCount);
-- 
1.9.3

