]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9121 dynlist enhancements
authorHoward Chu <hyc@openldap.org>
Mon, 16 Dec 2019 18:31:12 +0000 (18:31 +0000)
committerHoward Chu <hyc@openldap.org>
Mon, 16 Dec 2019 18:31:12 +0000 (18:31 +0000)
1) allow filtering on dynamic attribute values
2) populate an optionally configured memberOf attribute

test044 script still needs to be extended to test these
enhancements. We need to define an interim attributeType
for testing memberOf functionality.

doc/man/man5/slapo-dynlist.5
servers/slapd/overlays/dynlist.c
tests/data/dynlist.out
tests/scripts/test044-dynlist

index 83dba92b9b6703bbe2966af1164233e75f49be35..c86f9a696c0c2e20e5c482ce3824d0e172ab1c7e 100644 (file)
@@ -19,17 +19,11 @@ of the attributes listed in the URI are added to the original
 entry.
 No recursion is allowed, to avoid potential infinite loops.
 
-Since the resulting entry is dynamically constructed,
-it does not exist until it is constructed while being returned.
-As a consequence, dynamically added attributes do not participate
-in the filter matching phase of the search request handling.
-In other words, \fIfiltering for dynamically added attributes always fails\fP.
-
 The resulting entry must comply with the LDAP data model, so constraints
 are enforced.
 For example, if a \fISINGLE\-VALUE\fP attribute is listed,
 only the first value found during the list expansion appears in the final entry.
-The above described behavior is disabled when the \fImanageDSAit\fP
+All dynamic behavior is disabled when the \fImanageDSAit\fP
 control (RFC 3296) is used.
 In that case, the contents of the dynamic group entry is returned;
 namely, the URLs are returned instead of being expanded.
@@ -57,7 +51,7 @@ occurrences, and it must appear after the
 .B overlay
 directive.
 .TP
-.B dynlist\-attrset <group-oc> [<URI>] <URL-ad> [[<mapped-ad>:]<member-ad> ...]
+.B dynlist\-attrset <group-oc> [<URI>] <URL-ad> [[<mapped-ad>:]<member-ad>[@<memberOf-ad] ...]
 The value 
 .B group\-oc
 is the name of the objectClass that triggers the dynamic expansion of the
@@ -96,6 +90,10 @@ of the URI were present in the
 entry as values of the
 .B member-ad
 attribute.
+If the optional
+.B memberOf-ad
+attribute is also specified, then it will be populated with the DNs of the
+dynamic groups that an entry is a member of.
 
 Alternatively, 
 .B mapped-ad
@@ -103,7 +101,9 @@ can be used to remap attributes obtained through expansion.
 .B member-ad
 attributes are not filled by expanded DN, but are remapped as
 .B mapped-ad 
-attributes.  Multiple mapping statements can be used.
+attributes.  Multiple mapping statements can be used. The
+.B memberOf-ad
+option is not used in this case.
 
 .LP
 The dynlist overlay may be used with any backend, but it is mainly 
index 89c7c62968f4d0127c6f56590d5142af0f4ec2bb..99b2f8a565d6299f08d5d1596d621a180cce9247 100644 (file)
@@ -41,6 +41,7 @@ static AttributeDescription *ad_dgIdentity, *ad_dgAuthz;
 typedef struct dynlist_map_t {
        AttributeDescription    *dlm_member_ad;
        AttributeDescription    *dlm_mapped_ad;
+       AttributeDescription    *dlm_memberOf_ad;
        struct dynlist_map_t    *dlm_next;
 } dynlist_map_t;
 
@@ -57,7 +58,7 @@ typedef struct dynlist_info_t {
 } dynlist_info_t;
 
 #define DYNLIST_USAGE \
-       "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad> ...]\": "
+       "\"dynlist-attrset <oc> [uri] <URL-ad> [[<mapped-ad>:]<member-ad>[@<memberOf-ad>] ...]\": "
 
 static dynlist_info_t *
 dynlist_is_dynlist_next( Operation *op, SlapReply *rs, dynlist_info_t *old_dli )
@@ -113,10 +114,9 @@ dynlist_is_dynlist_next( Operation *op, SlapReply *rs, dynlist_info_t *old_dli )
 }
 
 static int
-dynlist_make_filter( Operation *op, Entry *e, const char *url, struct berval *oldf, struct berval *newf )
+dynlist_make_filter( Operation *op, Entry *e, dynlist_info_t *dli, const char *url, struct berval *oldf, struct berval *newf )
 {
        slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
-       dynlist_info_t  *dli = (dynlist_info_t *)on->on_bi.bi_private;
 
        char            *ptr;
        int             needBrackets = 0;
@@ -549,7 +549,7 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
                } else {
                        struct berval   flt;
                        ber_str2bv( lud->lud_filter, 0, 0, &flt );
-                       if ( dynlist_make_filter( op, rs->sr_entry, url->bv_val, &flt, &o.ors_filterstr ) ) {
+                       if ( dynlist_make_filter( op, rs->sr_entry, dli, url->bv_val, &flt, &o.ors_filterstr ) ) {
                                /* error */
                                goto cleanup;
                        }
@@ -563,6 +563,7 @@ dynlist_prepare_entry( Operation *op, SlapReply *rs, dynlist_info_t *dli )
                if ( o.o_bd && o.o_bd->be_search ) {
                        SlapReply       r = { REP_SEARCH };
                        r.sr_attr_flags = slap_attr_flags( o.ors_attrs );
+                       o.o_managedsait = SLAP_CONTROL_CRITICAL;
                        (void)o.o_bd->be_search( &o, &r );
                }
 
@@ -636,11 +637,20 @@ dynlist_compare( Operation *op, SlapReply *rs )
        Entry *e = NULL;
        dynlist_map_t *dlm;
        BackendDB *be;
+       int ret = SLAP_CB_CONTINUE;
+
+       if ( get_manageDSAit( op ) )
+               return SLAP_CB_CONTINUE;
 
        for ( ; dli != NULL; dli = dli->dli_next ) {
-               for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next )
-                       if ( op->oq_compare.rs_ava->aa_desc == dlm->dlm_member_ad )
+               for ( dlm = dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+                       AttributeDescription *ad = dlm->dlm_mapped_ad ? dlm->dlm_mapped_ad : dlm->dlm_member_ad;
+                       /* builtin dyngroup evaluator only works for DNs */
+                       if ( ad->ad_type->sat_syntax != slap_schema.si_syn_distinguishedName )
+                               continue;
+                       if ( op->oq_compare.rs_ava->aa_desc == ad )
                                break;
+               }
 
                if ( dlm ) {
                        /* This compare is for one of the attributes we're
@@ -696,7 +706,8 @@ dynlist_compare( Operation *op, SlapReply *rs )
 done:;
                        if ( id ) ber_bvarray_free_x( id, o.o_tmpmemctx );
 
-                       return SLAP_CB_CONTINUE;
+                       send_ldap_result( op, rs );
+                       return rs->sr_err;
                }
        }
 
@@ -746,12 +757,8 @@ done:;
        /* generate dynamic list with dynlist_response() and compare */
        {
                SlapReply       r = { REP_SEARCH };
-               dynlist_cc_t    dc = { { 0, dynlist_sc_compare_entry, 0, 0 }, 0 };
-               AttributeName   an[2];
-
-               dc.dc_ava = op->orc_ava;
-               dc.dc_res = &rs->sr_err;
-               o.o_callback = (slap_callback *) &dc;
+               Attribute *a;
+               AttributeName an[2];
 
                o.o_tag = LDAP_REQ_SEARCH;
                o.ors_limit = NULL;
@@ -768,15 +775,29 @@ done:;
                BER_BVZERO( &an[1].an_name );
                o.ors_attrs = an;
                o.ors_attrsonly = 0;
+               r.sr_entry = e;
+               r.sr_attrs = an;
 
                o.o_acl_priv = ACL_COMPARE;
+               dynlist_prepare_entry( &o, &r, dli );
+               a = attrs_find( r.sr_entry->e_attrs, op->orc_ava->aa_desc );
 
-               o.o_bd = be;
-               (void)be->be_search( &o, &r );
-
-               if ( o.o_dn.bv_val != op->o_dn.bv_val ) {
-                       slap_op_groups_free( &o );
+               ret = LDAP_NO_SUCH_ATTRIBUTE;
+               for ( ; a ; a = attrs_find( a->a_next, op->orc_ava->aa_desc )) {
+                       ret = LDAP_COMPARE_FALSE;
+                       if ( attr_valfind( a,
+                                       SLAP_MR_ATTRIBUTE_VALUE_NORMALIZED_MATCH |
+                                               SLAP_MR_ASSERTED_VALUE_NORMALIZED_MATCH,
+                                       &op->orc_ava->aa_value, NULL, op->o_tmpmemctx ) == LDAP_SUCCESS ) {
+                               ret = LDAP_COMPARE_TRUE;
+                               break;
+                       }
                }
+               rs->sr_err = ret;
+
+               if ( r.sr_entry != e )
+                       entry_free( r.sr_entry );
+               send_ldap_result( op, rs );
        }
 
 release:;
@@ -784,9 +805,225 @@ release:;
                overlay_entry_release_ov( &o, e, 0, on );
        }
 
+       return ret;
+}
+
+static int
+ad_infilter( Filter *f, AttributeDescription *ad )
+{
+       if ( !f )
+               return 0;
+
+       switch( f->f_choice & SLAPD_FILTER_MASK ) {
+       case SLAPD_FILTER_COMPUTED:
+               return 0;
+       case LDAP_FILTER_PRESENT:
+               return f->f_desc == ad;
+       case LDAP_FILTER_EQUALITY:
+       case LDAP_FILTER_GE:
+       case LDAP_FILTER_LE:
+       case LDAP_FILTER_APPROX:
+       case LDAP_FILTER_SUBSTRINGS:
+       case LDAP_FILTER_EXT:
+               return f->f_av_desc == ad;
+       case LDAP_FILTER_AND:
+       case LDAP_FILTER_OR:
+       case LDAP_FILTER_NOT: {
+               int ret = 0;
+               for ( f = f->f_list; f; f = f->f_next )
+                       ret |= ad_infilter( f, ad );
+               return ret;
+               }
+       }
+       return 0;
+}
+
+typedef struct dynlist_name_t {
+       struct berval dy_name;
+       dynlist_info_t *dy_dli;
+       int dy_seen;
+} dynlist_name_t;
+
+typedef struct dynlist_search_t {
+       TAvlnode *ds_names;
+       dynlist_info_t *ds_dli;
+} dynlist_search_t;
+
+static int
+dynlist_avl_cmp( const void *c1, const void *c2 )
+{
+       const dynlist_name_t *n1, *n2;
+       int rc;
+       n1 = c1; n2 = c2;
+
+       rc = n1->dy_name.bv_len - n2->dy_name.bv_len;
+       if ( rc ) return rc;
+       return ber_bvcmp( &n1->dy_name, &n2->dy_name );
+}
+
+/* build a list of dynamic entries */
+static int
+dynlist_search1resp( Operation *op, SlapReply *rs )
+{
+       if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+               dynlist_search_t *ds = op->o_callback->sc_private;
+               dynlist_name_t *dyn = ch_calloc(1, sizeof(dynlist_name_t)+rs->sr_entry->e_nname.bv_len + 1);
+               dyn->dy_name.bv_val = (void *)(dyn+1);
+               dyn->dy_dli = ds->ds_dli;
+               dyn->dy_name.bv_len = rs->sr_entry->e_nname.bv_len;
+               memcpy(dyn->dy_name.bv_val, rs->sr_entry->e_nname.bv_val, rs->sr_entry->e_nname.bv_len );
+               if ( tavl_insert( &ds->ds_names, dyn, dynlist_avl_cmp, avl_dup_error ))
+                       ch_free( dyn );
+       }
+       return 0;
+}
+
+static int
+dynlist_search_cleanup( Operation *op, SlapReply *rs )
+{
+       if ( rs->sr_type == REP_RESULT || op->o_abandon ||
+               rs->sr_err == SLAPD_ABANDON ) {
+               slap_callback *sc = op->o_callback;
+               dynlist_search_t *ds = op->o_callback->sc_private;
+               tavl_free( ds->ds_names, ch_free );
+               op->o_callback = sc->sc_next;
+               op->o_tmpfree( sc, op->o_tmpmemctx );
+       }
+       return 0;
+}
+
+/* process the search responses */
+static int
+dynlist_search2resp( Operation *op, SlapReply *rs )
+{
+       dynlist_search_t *ds = op->o_callback->sc_private;
+       dynlist_name_t *dyn;
+       int rc;
+
+       if ( rs->sr_type == REP_SEARCH && rs->sr_entry != NULL ) {
+               dyn = tavl_find( ds->ds_names, &rs->sr_entry->e_nname, dynlist_avl_cmp );
+               if ( dyn ) {
+                       dyn->dy_seen = 1;
+                       rc = dynlist_prepare_entry( op, rs, dyn->dy_dli );
+                       return rc;
+               } else {
+                       TAvlnode *ptr;
+                       Entry *e = rs->sr_entry;
+                       for ( ptr = tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+                               ptr = tavl_next( ptr, TAVL_DIR_RIGHT )) {
+                               dynlist_map_t *dlm;
+                               dyn = ptr->avl_data;
+                               for ( dlm = dyn->dy_dli->dli_dlm; dlm; dlm = dlm->dlm_next ) {
+                                       if ( dlm->dlm_memberOf_ad ) {
+                                               rc = backend_group( op, NULL, &dyn->dy_name,
+                                                       &e->e_nname, dyn->dy_dli->dli_oc, dyn->dy_dli->dli_ad );
+                                               if ( rc == LDAP_SUCCESS ) {
+                                                       /* ensure e is modifiable, but do not replace
+                                                        * sr_entry yet since we have pointers into it */
+                                                       if ( !( rs->sr_flags & REP_ENTRY_MODIFIABLE ) ) {
+                                                               e = entry_dup( rs->sr_entry );
+                                                       }
+                                                       attr_merge_one( e, dlm->dlm_memberOf_ad, &dyn->dy_name, &dyn->dy_name );
+                                               }
+                                       }
+                               }
+                       }
+                       if ( e != rs->sr_entry ) {
+                               rs_replace_entry( op, rs, (slap_overinst *)op->o_bd->bd_info, e );
+                               rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+                       }
+               }
+       } else if ( rs->sr_type == REP_RESULT ) {
+               TAvlnode *ptr;
+               SlapReply r = *rs;
+               for ( ptr = tavl_end( ds->ds_names, TAVL_DIR_LEFT ); ptr;
+                       ptr = tavl_next( ptr, TAVL_DIR_RIGHT )) {
+                       dyn = ptr->avl_data;
+                       if ( dyn->dy_seen )
+                               continue;
+                       if ( !dnIsSuffixScope( &dyn->dy_name, &op->o_req_ndn, op->ors_scope ))
+                               continue;
+                       if ( overlay_entry_get_ov( op, &dyn->dy_name, NULL, NULL, 0, &r.sr_entry, (slap_overinst *)op->o_bd->bd_info ) != LDAP_SUCCESS ||
+                               r.sr_entry == NULL )
+                               continue;
+                       r.sr_flags = REP_ENTRY_MUSTRELEASE;
+                       dynlist_prepare_entry( op, &r, dyn->dy_dli );
+                       send_search_entry( op, &r );
+               }
+               rs->sr_nentries = r.sr_nentries;
+       }
        return SLAP_CB_CONTINUE;
 }
 
+static int
+dynlist_search( Operation *op, SlapReply *rs )
+{
+       slap_overinst   *on = (slap_overinst *)op->o_bd->bd_info;
+       dynlist_info_t  *dli = (dynlist_info_t *)on->on_bi.bi_private;
+       Operation o = *op;
+       dynlist_map_t *dlm;
+       Filter f;
+       AttributeAssertion ava;
+       AttributeName an[2] = {0};
+
+       slap_callback *sc;
+       dynlist_search_t *ds;
+
+       if ( get_manageDSAit( op ) )
+               return SLAP_CB_CONTINUE;
+
+       sc = op->o_tmpcalloc( 1, sizeof(slap_callback)+sizeof(dynlist_search_t), op->o_tmpmemctx );
+       sc->sc_private = (void *)(sc+1);
+       ds = sc->sc_private;
+
+       f.f_choice = LDAP_FILTER_EQUALITY;
+       f.f_ava = &ava;
+       f.f_av_desc = slap_schema.si_ad_objectClass;
+       f.f_next = NULL;
+       o.o_managedsait = SLAP_CONTROL_CRITICAL;
+
+       /* Find all dyngroups in tree. For group expansion
+        * we only need the groups within the search scope, but
+        * for memberOf populating, we need all dyngroups.
+        */
+       for ( ; dli != NULL; dli = dli->dli_next ) {
+               if ( o.o_callback != sc ) {
+                       o.o_callback = sc;
+                       o.ors_filter = &f;
+                       o.o_req_dn = op->o_bd->be_suffix[0];
+                       o.o_req_ndn = op->o_bd->be_nsuffix[0];
+                       o.ors_scope = LDAP_SCOPE_SUBTREE;
+                       o.ors_attrsonly = 0;
+                       o.ors_attrs = an;
+                       o.o_bd = select_backend( op->o_bd->be_nsuffix, 1 );
+                       BER_BVZERO( &o.ors_filterstr );
+                       sc->sc_response = dynlist_search1resp;
+               }
+               ds->ds_dli = dli;
+               f.f_av_value = dli->dli_oc->soc_cname;
+               if ( o.ors_filterstr.bv_val )
+                       o.o_tmpfree( o.ors_filterstr.bv_val, o.o_tmpmemctx );
+               filter2bv_x( &o, &f, &o.ors_filterstr );
+               an[0].an_desc = dli->dli_ad;
+               an[0].an_name = dli->dli_ad->ad_cname;
+               {
+                       SlapReply       r = { REP_SEARCH };
+                       (void)o.o_bd->be_search( &o, &r );
+               }
+       }
+
+       if ( ds->ds_names != NULL ) {
+               sc->sc_response = dynlist_search2resp;
+               sc->sc_cleanup = dynlist_search_cleanup;
+               sc->sc_next = op->o_callback;
+               op->o_callback = sc;
+       } else {
+               op->o_tmpfree( sc, op->o_tmpmemctx );
+       }
+       return SLAP_CB_CONTINUE;
+}
+
+#if 0
 static int
 dynlist_response( Operation *op, SlapReply *rs )
 {
@@ -824,6 +1061,7 @@ dynlist_response( Operation *op, SlapReply *rs )
 
        return SLAP_CB_CONTINUE;
 }
+#endif
 
 static int
 dynlist_build_def_filter( dynlist_info_t *dli )
@@ -933,6 +1171,11 @@ dl_cfgen( ConfigArgs *c )
                                        }
                                                
                                        ptr = lutil_strcopy( ptr, dlm->dlm_member_ad->ad_cname.bv_val );
+
+                                       if ( dlm->dlm_memberOf_ad ) {
+                                               *ptr++ = '@';
+                                               ptr = lutil_strcopy( ptr, dlm->dlm_memberOf_ad->ad_cname.bv_val );
+                                       }
                                }
 
                                bv.bv_val = c->cr_msg;
@@ -1199,6 +1442,7 @@ done_uri:;
                        char *cp;
                        AttributeDescription *member_ad = NULL;
                        AttributeDescription *mapped_ad = NULL;
+                       AttributeDescription *memberOf_ad = NULL;
                        dynlist_map_t *dlmp;
 
 
@@ -1223,6 +1467,22 @@ done_uri:;
                                }
                                arg = cp + 1;
                        }
+                       if ( ( cp = strchr( arg, '@' ) ) != NULL ) {
+                               struct berval bv;
+                               ber_str2bv( cp+1, 0, 0, &bv );
+                               rc = slap_bv2ad( &bv, &memberOf_ad, &text );
+                               if ( rc != LDAP_SUCCESS ) {
+                                       snprintf( c->cr_msg, sizeof( c->cr_msg ),
+                                               DYNLIST_USAGE
+                                               "unable to find memberOf AttributeDescription #%d \"%s\"\n",
+                                               i - 3, c->argv[ i ] );
+                                       Debug( LDAP_DEBUG_ANY, "%s: %s.\n",
+                                               c->log, c->cr_msg );
+                                       rc = 1;
+                                       goto done_uri;
+                               }
+                               *cp = '\0';
+                       }
 
                        rc = slap_str2ad( arg, &member_ad, &text );
                        if ( rc != LDAP_SUCCESS ) {
@@ -1242,6 +1502,7 @@ done_uri:;
                        }
                        dlmp->dlm_member_ad = member_ad;
                        dlmp->dlm_mapped_ad = mapped_ad;
+                       dlmp->dlm_memberOf_ad = memberOf_ad;
                        dlmp->dlm_next = NULL;
                
                        if ( dlml != NULL ) 
@@ -1550,7 +1811,8 @@ dynlist_initialize(void)
        dynlist.on_bi.bi_db_open = dynlist_db_open;
        dynlist.on_bi.bi_db_destroy = dynlist_db_destroy;
 
-       dynlist.on_response = dynlist_response;
+       dynlist.on_bi.bi_op_search = dynlist_search;
+       dynlist.on_bi.bi_op_compare = dynlist_compare;
 
        dynlist.on_bi.bi_cf_ocs = dlocs;
 
index 8caf0e22ff33b73da785296a447af843e0e94ec6..2cdfdc2ad9b7f7b70a26a5cbb5f8550c04169507 100644 (file)
@@ -204,8 +204,9 @@ FALSE
 # Testing list compare (should return FALSE)...
 FALSE
 
-# Testing list compare with manageDSAit...
-FALSE
+# Testing list compare with manageDSAit (should return UNDEFINED)...
+Compare Result: No such attribute (16)
+UNDEFINED
 
 # Testing list search without dgIdentity...
 dn: cn=Dynamic List of Members,ou=Dynamic Lists,dc=example,dc=com
index 86885cd1150f765d4e42695947fcb6f63965a073..3bb5371e3ce3c5ebf0ad29cdbdc7dd740e839f56 100755 (executable)
@@ -526,8 +526,8 @@ case $RC in
 esac
 echo "" >> $SEARCHOUT
 
-echo "Testing list compare with manageDSAit..."
-echo "# Testing list compare with manageDSAit..." >> $SEARCHOUT
+echo "Testing list compare with manageDSAit (should return UNDEFINED)..."
+echo "# Testing list compare with manageDSAit (should return UNDEFINED)..." >> $SEARCHOUT
 $LDAPCOMPARE -h $LOCALHOST -p $PORT1 -MM \
        "cn=Dynamic List,$LISTDN" "member:$CMPDN" \
        >> $SEARCHOUT 2>&1
@@ -535,12 +535,17 @@ RC=$?
 case $RC in
 5)
        echo "ldapcompare returned FALSE ($RC)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
        ;;
 6)
        echo "ldapcompare returned TRUE ($RC)!"
        test $KILLSERVERS != no && kill -HUP $KILLPIDS
        exit $RC
        ;;
+16|32)
+       echo "ldapcompare returned UNDEFINED ($RC)"
+       ;;
 0)
        echo "ldapcompare returned success ($RC)!"
        test $KILLSERVERS != no && kill -HUP $KILLPIDS