From 12c6814a2b19cd67b3203e8105b426a0fe900fdf Mon Sep 17 00:00:00 2001 From: Thierry Bordaz Date: Apr 15 2020 16:50:30 +0000 Subject: Ticket 50931 - RFE AD filter rewriter for ObjectCategory Bug Description: AD provides flexibility, to AD client, to use shortcuts values in filter components. To support AD client using 'ObjectCategory' shortcut, we need a 389-ds filter rewriters that translate the filter '(ObjectCategory=foo)' into '(ObjectCategory=cn=foo,cn=schema,cn=configuration,)' before processing the filter [1] https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Filter_on_objectCategory_and_objectClass Fix Description: This patch uses the new ability to registers rewriters (https://pagure.io/389-ds-base/issue/50980). It implements a new rewriters library (under /lib) with a callback filter rewriter adfilter_rewrite_objectCategory. A adfilter rewriter config entry refers to that library and register the callback at startup https://pagure.io/389-ds-base/issue/50931 Reviewed by: Mark Reynolds, William Brown Platforms tested: F29 Flag Day: no Doc impact: no --- diff --git a/Makefile.am b/Makefile.am index eda1ad6..bd875dc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -82,6 +82,8 @@ else SDS_INCLUDES = -I$(srcdir)/src/libsds/include/ -I$(srcdir)/src/libsds/external/ endif +REWRITERS_INCLUDES = -I$(srcdir)/src/rewriters/ + SVRCORE_INCLUDES = -I$(srcdir)/src/svrcore/src/ # the -U undefines these symbols - should use the corresponding DS_ ones instead - see configure.ac @@ -364,7 +366,7 @@ endif # based on defines # ---------------------------------------------------------------------------------------- -server_LTLIBRARIES = libsds.la libslapd.la libldaputil.la libns-dshttpd.la +server_LTLIBRARIES = libsds.la libslapd.la libldaputil.la libns-dshttpd.la librewriters.la lib_LTLIBRARIES = libsvrcore.la @@ -1150,6 +1152,16 @@ libldaputil_la_LDFLAGS = $(AM_LDFLAGS) #//////////////////////////////////////////////////////////////// #------------------------ +# librewriters +#------------------------ +librewriters_la_SOURCES = \ + src/rewriters/adfilter.c + +librewriters_la_LDFLAGS = $(AM_LDFLAGS) +librewriters_la_CPPFLAGS = $(AM_CPPFLAGS) $(REWRITERS_INCLUDES) $(DSPLUGIN_CPPFLAGS) +librewriters_la_LIBADD = $(NSS_LINK) $(NSPR_LINK) + +#------------------------ # libsvrcore #------------------------ libsvrcore_la_SOURCES = \ diff --git a/dirsrvtests/tests/suites/rewriters/adfilter_test.py b/dirsrvtests/tests/suites/rewriters/adfilter_test.py new file mode 100644 index 0000000..d2256d6 --- /dev/null +++ b/dirsrvtests/tests/suites/rewriters/adfilter_test.py @@ -0,0 +1,85 @@ +import pytest +import glob +from lib389.tasks import * +from lib389.utils import * +from lib389.topologies import topology_st + +from lib389._constants import DEFAULT_SUFFIX, HOST_STANDALONE, PORT_STANDALONE + +log = logging.getLogger(__name__) +# Skip on versions 1.4.2 and before. Rewriters are expected in 1.4.3 +pytestmark = [pytest.mark.tier2, + pytest.mark.skipif(ds_is_older('1.4.3'), reason="Not implemented")] + +PW = 'password' +configuration_container = 'cn=Configuration,%s' % DEFAULT_SUFFIX +schema_container = "cn=Schema,%s" % configuration_container + +def _create_ad_objects_container(inst): + inst.add_s(Entry(( + configuration_container, { + 'objectClass': 'top nsContainer'.split(), + 'cn': 'Configuration' + }))) + inst.add_s(Entry(( + schema_container, { + 'objectClass': 'top nsContainer'.split(), + 'cn': 'Schema' + }))) + +def _create_user(inst, name, salt): + dn = 'cn=%s,%s' % (name, schema_container) + inst.add_s(Entry(( + dn, { + 'objectClass': 'top person extensibleobject'.split(), + 'cn': name, + 'sn': name, + 'objectcategory': dn, + "description" : salt, + 'userpassword': PW + }))) + + + +def test_adfilter_objectCategory(topology_st): + """ + Test adfilter objectCategory rewriter function + """ + + librewriters = os.path.join( topology_st.standalone.ds_paths.lib_dir, 'dirsrv/librewriters.so') + assert librewriters + # register objectCategory rewriter + topology_st.standalone.add_s(Entry(( + "cn=adfilter,cn=rewriters,cn=config", { + "objectClass": "top rewriterEntry".split(), + "cn": "adfilter", + "nsslapd-libpath": librewriters, + "nsslapd-filterrewriter": "adfilter_rewrite_objectCategory", + } + ))) + + objectcategory_attr = '( NAME \'objectCategory\' DESC \'test of objectCategory\' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )' + topology_st.standalone.schema.add_schema('attributetypes', [ensure_bytes(objectcategory_attr)]) + + topology_st.standalone.restart(60) + + # Add a user + _create_ad_objects_container(topology_st.standalone) + for i in range(0, 20): + _create_user(topology_st.standalone, "user_%d" % i, str(i)) + + # Check EQUALITY filter rewrite => it should match only one entry + for i in range(0, 20): + ents = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, '(objectCategory=user_%d)' % i) + assert len(ents) == 1 + + # Check SUBSTRING search is not replaced + ents = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, '(objectCategory=user_*)') + assert len(ents) == 0 + + # Check PRESENCE search is not replaced so it selects all entries having objectCategory + ents = topology_st.standalone.search_s(DEFAULT_SUFFIX, ldap.SCOPE_SUBTREE, '(objectCategory=*)') + assert len(ents) == 20 + + log.info('Test PASSED') + diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif index f4123f2..c1fd7f5 100644 --- a/ldap/schema/01core389.ldif +++ b/ldap/schema/01core389.ldif @@ -314,6 +314,9 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2353 NAME 'nsslapd-encryptionalgorithm' attributeTypes: ( 2.16.840.1.113730.3.1.2084 NAME 'nsSymmetricKey' DESC 'A symmetric key - currently used by attribute encryption' SYNTAX 1.3.6.1.4.1.1466.115.121.1.40 SINGLE-VALUE X-ORIGIN 'attribute encryption' ) attributeTypes: ( 2.16.840.1.113730.3.1.2364 NAME 'nsds5replicaLastInitStatusJSON' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION X-ORIGIN 'Netscape Directory Server' ) attributeTypes: ( 2.16.840.1.113730.3.1.2365 NAME 'nsds5replicaLastUpdateStatusJSON' DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE NO-USER-MODIFICATION X-ORIGIN 'Netscape Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2367 NAME 'nsslapd-libPath' DESC 'Rewriter shared library path' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2368 NAME 'nsslapd-filterrewriter' DESC 'Filter rewriter function name' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) +attributeTypes: ( 2.16.840.1.113730.3.1.2369 NAME 'nsslapd-returnedAttrRewriter' DESC 'Returned attribute rewriter function name' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 SINGLE-VALUE X-ORIGIN '389 Directory Server' ) # # objectclasses # @@ -334,3 +337,4 @@ objectClasses: ( nsEncryptionModule-oid NAME 'nsEncryptionModule' DESC 'Netscape objectClasses: ( 2.16.840.1.113730.3.2.327 NAME 'rootDNPluginConfig' DESC 'Netscape defined objectclass' SUP top MUST ( cn ) MAY ( rootdn-open-time $ rootdn-close-time $ rootdn-days-allowed $ rootdn-allow-host $ rootdn-deny-host $ rootdn-allow-ip $ rootdn-deny-ip ) X-ORIGIN 'Netscape' ) objectClasses: ( 2.16.840.1.113730.3.2.328 NAME 'nsSchemaPolicy' DESC 'Netscape defined objectclass' SUP top MAY ( cn $ schemaUpdateObjectclassAccept $ schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject) X-ORIGIN 'Netscape Directory Server' ) objectClasses: ( 2.16.840.1.113730.3.2.332 NAME 'nsChangelogConfig' DESC 'Configuration of the changelog5 object' SUP top MUST ( cn $ nsslapd-changelogdir ) MAY ( nsslapd-changelogmaxage $ nsslapd-changelogtrim-interval $ nsslapd-changelogmaxentries $ nsslapd-changelogsuffix $ nsslapd-changelogcompactdb-interval $ nsslapd-encryptionalgorithm $ nsSymmetricKey ) X-ORIGIN '389 Directory Server' ) +objectClasses: ( 2.16.840.1.113730.3.2.337 NAME 'rewriterEntry' DESC '' SUP top MUST ( nsslapd-libPath ) MAY ( cn $ nsslapd-filterrewriter $ nsslapd-returnedAttrRewriter ) X-ORIGIN '389 Directory Server' ) diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in index 36fc3cd..d633732 100644 --- a/rpm/389-ds-base.spec.in +++ b/rpm/389-ds-base.spec.in @@ -675,6 +675,7 @@ exit 0 %{_libdir}/%{pkgname}/libns-dshttpd-*.so %{_libdir}/%{pkgname}/libsds.so.* %{_libdir}/%{pkgname}/libldaputil.so.* +%{_libdir}/%{pkgname}/librewriters.so* %if %{bundle_jemalloc} %{_libdir}/%{pkgname}/lib/libjemalloc.so.2 %endif diff --git a/src/rewriters/adfilter.c b/src/rewriters/adfilter.c new file mode 100644 index 0000000..35125fc --- /dev/null +++ b/src/rewriters/adfilter.c @@ -0,0 +1,126 @@ +/** BEGIN COPYRIGHT BLOCK + * Copyright (C) 2020 Red Hat, Inc. + * All rights reserved. + * + * License: GPL (version 3 or any later version). + * See LICENSE for details. + * END COPYRIGHT BLOCK **/ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/* + * AD rewriters + * + * This library contains filter rewriters and computed attribute rewriters. + */ + +#include "slap.h" + +static char *rewriter_name = "filter rewriter adfilter"; + +#define OBJECTCATEGORY "objectCategory" + + +/* Rewrite ObjectCategory as described in [1] + * [1] https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx#Filter_on_objectCategory_and_objectClass + * static char *objectcategory_shortcuts[] = {"person", "computer", "user", "contact", "group", "organizationalPerson", NULL}; + */ + +typedef struct { + char *attrtype; /* type = objectCategory */ + char *format; +} objectCategory_arg_t; + +static int +substitute_shortcut(Slapi_Filter *f, void *arg) +{ + objectCategory_arg_t *substitute_arg = (objectCategory_arg_t *) arg; + char *filter_type; + struct berval *bval; + char *newval; + char logbuf[1024] = {0}; + + if ((substitute_arg == NULL) || + (substitute_arg->attrtype == NULL) || + (substitute_arg->format == NULL)) { + return SLAPI_FILTER_SCAN_STOP; + } + + /* If (objectCategory=) --> (objectCategory=cn=,cn=Schema,cn=Configuration,) */ + if ((slapi_filter_get_ava(f, &filter_type, &bval) == 0) && + (slapi_filter_get_choice(f) == LDAP_FILTER_EQUALITY) && + (bval->bv_val) && + (strcasecmp(filter_type, substitute_arg->attrtype) == 0)) { + newval = slapi_ch_smprintf(substitute_arg->format, bval->bv_val); + slapi_log_err(SLAPI_LOG_FILTER, rewriter_name, "objectcategory_check_filter - 1 component %s : %s -> %s\n", + slapi_filter_to_string(f, logbuf, sizeof (logbuf)), + bval->bv_val, + newval); + slapi_ch_free_string(&bval->bv_val); + bval->bv_val = newval; + bval->bv_len = strlen(newval); + } + + /* Return continue because we should + * substitute 'from' in all filter components + */ + return SLAPI_FILTER_SCAN_CONTINUE; +} + +/* + * This is a filter rewriter function for 'ObjectCagerory' attribute + * + * Its rewriter config entry looks like + * dn: cn=adfilter,cn=rewriters,cn=config + * objectClass: top + * objectClass: extensibleObject + * cn: adfilter + * nsslapd-libpath: librewriters + * nsslapd-filterrewriter: adfilter_rewrite_objectCategory + */ +int32_t +adfilter_rewrite_objectCategory(Slapi_PBlock *pb) +{ + Slapi_Filter *clientFilter = NULL; + Slapi_DN *sdn = NULL; + Slapi_Backend *be = NULL; + const char *be_suffix = NULL; + int error_code = 0; + int rc; + char *format; + char *strFilter; + objectCategory_arg_t arg; + + slapi_pblock_get(pb, SLAPI_SEARCH_FILTER, &clientFilter); + slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &strFilter); + slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &sdn); + + if (strFilter && (strcasestr(strFilter, OBJECTCATEGORY) == NULL)) { + /* accelerator: returns if filter string does not contain objectcategory */ + return SEARCH_REWRITE_CALLBACK_CONTINUE; + } + if ((be = slapi_be_select(sdn)) != NULL) { + be_suffix = slapi_sdn_get_dn(slapi_be_getsuffix(be, 0)); + } + + /* prepare the argument of filter apply callback: a format and + * the attribute type that trigger the rewrite + */ + format = slapi_ch_smprintf("cn=%s,cn=Schema,cn=Configuration,%s", (char *) "%s", (char *) be_suffix); + arg.attrtype = OBJECTCATEGORY; + arg.format = format; + + /* Now apply substitute_shortcut on each filter component */ + rc = slapi_filter_apply(clientFilter, substitute_shortcut, &arg, &error_code); + slapi_ch_free_string(&format); + if (rc == SLAPI_FILTER_SCAN_NOMORE) { + return SEARCH_REWRITE_CALLBACK_CONTINUE; /* Let's others rewriter play */ + } else { + slapi_log_err(SLAPI_LOG_ERR, + "adfilter_rewrite_objectCategory", "Could not update the search filter - error %d (%d)\n", + rc, error_code); + return SEARCH_REWRITE_CALLBACK_ERROR; /* operation error */ + } +} \ No newline at end of file