From 36f0f391d023716994e4025b285188f2ae94dc56 Mon Sep 17 00:00:00 2001 From: "Thierry bordaz (tbordaz)" Date: Wed, 15 Jan 2014 11:38:32 +0100 Subject: [PATCH] Ticket 47653 - Need a way to allow users to create entries assigned to themselves. Bug Description: Users need to be able to create, edit and delete their own entries. An entry (i.e. cn=token1_user1234,dc=example,dc=com) has an attribute (i.e. ipatokenOwner) that contains the entry DN of the user (i.e. uid=user1234,dc=example,dc=com). Being bound as 'uid=user1234,dc=example,dc=com', we should be able to any ldap operation on 'user1234' entries like cn=token1_user1234. Fix Description: It adds a BindRule: SELFDN, implemented in DS_LASSelfDnAttrEval (called by DS_LASUserAttrEval). The syntax in the aci is : = #SELFDN If the BindDN (lasinfo.clientDn) exists in entry. (lasinfo.resourceEntry[attrName]), then the aci matched https://fedorahosted.org/389/ticket/47653 Reviewed by: ? Platforms tested: F17 Flag Day: no Doc impact: no --- dirsrvtests/tickets/ticket47653_test.py | 412 ++++++++++++++++++++++++++++++++ ldap/servers/plugins/acl/acl.h | 1 + ldap/servers/plugins/acl/acllas.c | 262 +++++++++++++++++++- 3 files changed, 674 insertions(+), 1 deletion(-) create mode 100644 dirsrvtests/tickets/ticket47653_test.py diff --git a/dirsrvtests/tickets/ticket47653_test.py b/dirsrvtests/tickets/ticket47653_test.py new file mode 100644 index 0000000..a6b1ec0 --- /dev/null +++ b/dirsrvtests/tickets/ticket47653_test.py @@ -0,0 +1,412 @@ +import os +import sys +import time +import ldap +import logging +import socket +import time +import logging +import pytest +from lib389 import DirSrv, Entry, tools +from lib389.tools import DirSrvTools +from lib389._constants import * +from lib389.properties import * +from constants import * + +log = logging.getLogger(__name__) + +installation_prefix = None + +OC_NAME = 'OCticket47653' +MUST = "(postalAddress $ postalCode)" +MAY = "(member $ street)" + +OTHER_NAME = 'other_entry' +MAX_OTHERS = 10 + +BIND_NAME = 'bind_entry' +BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX) +BIND_PW = 'password' + +ENTRY_NAME = 'test_entry' +ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX) +ENTRY_OC = "top person %s" % OC_NAME + +def _oc_definition(oid_ext, name, must=None, may=None): + oid = "1.2.3.4.5.6.7.8.9.10.%d" % oid_ext + desc = 'To test ticket 47490' + sup = 'person' + if not must: + must = MUST + if not may: + may = MAY + + new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY %s )" % (oid, name, desc, sup, must, may) + return new_oc + + +class TopologyStandalone(object): + def __init__(self, standalone): + standalone.open() + self.standalone = standalone + + +@pytest.fixture(scope="module") +def topology(request): + ''' + This fixture is used to standalone topology for the 'module'. + At the beginning, It may exists a standalone instance. + It may also exists a backup for the standalone instance. + + Principle: + If standalone instance exists: + restart it + If backup of standalone exists: + create/rebind to standalone + + restore standalone instance from backup + else: + Cleanup everything + remove instance + remove backup + Create instance + Create backup + ''' + global installation_prefix + + if installation_prefix: + args_instance[SER_DEPLOYED_DIR] = installation_prefix + + standalone = DirSrv(verbose=False) + + # Args for the standalone instance + args_instance[SER_HOST] = HOST_STANDALONE + args_instance[SER_PORT] = PORT_STANDALONE + args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE + args_standalone = args_instance.copy() + standalone.allocate(args_standalone) + + # Get the status of the backups + backup_standalone = standalone.checkBackupFS() + + # Get the status of the instance and restart it if it exists + instance_standalone = standalone.exists() + if instance_standalone: + # assuming the instance is already stopped, just wait 5 sec max + standalone.stop(timeout=5) + standalone.start(timeout=10) + + if backup_standalone: + # The backup exist, assuming it is correct + # we just re-init the instance with it + if not instance_standalone: + standalone.create() + # Used to retrieve configuration information (dbdir, confdir...) + standalone.open() + + # restore standalone instance from backup + standalone.stop(timeout=10) + standalone.restoreFS(backup_standalone) + standalone.start(timeout=10) + + else: + # We should be here only in two conditions + # - This is the first time a test involve standalone instance + # - Something weird happened (instance/backup destroyed) + # so we discard everything and recreate all + + # Remove the backup. So even if we have a specific backup file + # (e.g backup_standalone) we clear backup that an instance may have created + if backup_standalone: + standalone.clearBackupFS() + + # Remove the instance + if instance_standalone: + standalone.delete() + + # Create the instance + standalone.create() + + # Used to retrieve configuration information (dbdir, confdir...) + standalone.open() + + # Time to create the backups + standalone.stop(timeout=10) + standalone.backupfile = standalone.backupFS() + standalone.start(timeout=10) + + # + # Here we have standalone instance up and running + # Either coming from a backup recovery + # or from a fresh (re)init + # Time to return the topology + return TopologyStandalone(standalone) + + +def test_ticket47653_init(topology): + """ + It adds + - Objectclass with MAY 'member' + - an entry ('bind_entry') with which we bind to test the 'SELFDN' operation + It deletes the anonymous aci + + """ + + + topology.standalone.log.info("Add %s that allows 'member' attribute" % OC_NAME) + new_oc = _oc_definition(2, OC_NAME, must = MUST, may = MAY) + topology.standalone.addSchema('objectClasses', new_oc) + + + # entry used to bind with + topology.standalone.log.info("Add %s" % BIND_DN) + topology.standalone.add_s(Entry((BIND_DN, { + 'objectclass': "top person".split(), + 'sn': BIND_NAME, + 'cn': BIND_NAME, + 'userpassword': BIND_PW}))) + + # enable acl error logging + mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '128')] + topology.standalone.modify_s(DN_CONFIG, mod) + + # get read of anonymous ACI for use 'read-search' aci in SEARCH test + ACI_ANONYMOUS = "(targetattr!=\"userPassword\")(version 3.0; acl \"Enable anonymous access\"; allow (read, search, compare) userdn=\"ldap:///anyone\";)" + mod = [(ldap.MOD_DELETE, 'aci', ACI_ANONYMOUS)] + topology.standalone.modify_s(SUFFIX, mod) + + # add dummy entries + for cpt in range(MAX_OTHERS): + name = "%s%d" % (OTHER_NAME, cpt) + topology.standalone.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), { + 'objectclass': "top person".split(), + 'sn': name, + 'cn': name}))) + + +def test_ticket47653_add(topology): + ''' + It checks that, bound as bind_entry, + - we can not ADD an entry without the proper SELFDN aci. + - with the proper ACI we can not ADD with 'member' attribute + - with the proper ACI and 'member' it succeeds to ADD + ''' + topology.standalone.log.info("\n\n######################### ADD ######################\n") + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # Prepare the entry with multivalued members + entry = Entry(ENTRY_DN) + entry.setValues('objectclass', 'top', 'person', 'OCticket47653') + entry.setValues('sn', ENTRY_NAME) + entry.setValues('cn', ENTRY_NAME) + entry.setValues('postalAddress', 'here') + entry.setValues('postalCode', '1234') + members = [] + for cpt in range(MAX_OTHERS): + name = "%s%d" % (OTHER_NAME, cpt) + members.append("cn=%s,%s" % (name, SUFFIX)) + members.append(BIND_DN) + entry.setValues('member', members) + + # entry to add WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS + try: + topology.standalone.log.info("Try to add Add %s (aci is missing): %r" % (ENTRY_DN, entry)) + + topology.standalone.add_s(entry) + except Exception as e: + topology.standalone.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ldap.INSUFFICIENT_ACCESS) + + + # Ok Now add the proper ACI + topology.standalone.log.info("Bind as %s and add the ADD SELFDN aci" % DN_DM) + topology.standalone.simple_bind_s(DN_DM, PASSWORD) + + ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX + ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME + ACI_ALLOW = "(version 3.0; acl \"SelfDN add\"; allow (add)" + ACI_SUBJECT = " userattr = \"member#selfDN\";)" + ACI_BODY = ACI_TARGET + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT + mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)] + topology.standalone.modify_s(SUFFIX, mod) + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # entry to add WITHOUT member and WITH the ACI -> ldap.INSUFFICIENT_ACCESS + try: + topology.standalone.log.info("Try to add Add %s (member is missing)" % ENTRY_DN) + topology.standalone.add_s(Entry((ENTRY_DN, { + 'objectclass': ENTRY_OC.split(), + 'sn': ENTRY_NAME, + 'cn': ENTRY_NAME, + 'postalAddress': 'here', + 'postalCode': '1234'}))) + except Exception as e: + topology.standalone.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ldap.INSUFFICIENT_ACCESS) + + topology.standalone.log.info("Try to add Add %s should be successful" % ENTRY_DN) + topology.standalone.add_s(entry) + +def test_ticket47653_search(topology): + ''' + It checks that, bound as bind_entry, + - we can not search an entry without the proper SELFDN aci. + - adding the ACI, we can search the entry + ''' + topology.standalone.log.info("\n\n######################### SEARCH ######################\n") + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # entry to search WITH member being BIND_DN but WITHOUT the ACI -> no entry returned + topology.standalone.log.info("Try to search %s (aci is missing)" % ENTRY_DN) + ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*') + assert len(ents) == 0 + + + # Ok Now add the proper ACI + topology.standalone.log.info("Bind as %s and add the READ/SEARCH SELFDN aci" % DN_DM) + topology.standalone.simple_bind_s(DN_DM, PASSWORD) + + ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX + ACI_TARGETATTR = "(targetattr = *)" + ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME + ACI_ALLOW = "(version 3.0; acl \"SelfDN search-read\"; allow (read, search, compare)" + ACI_SUBJECT = " userattr = \"member#selfDN\";)" + ACI_BODY = ACI_TARGET + ACI_TARGETATTR + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT + mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)] + topology.standalone.modify_s(SUFFIX, mod) + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # entry to search with the proper aci + topology.standalone.log.info("Try to search %s should be successful" % ENTRY_DN) + ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*') + assert len(ents) == 1 + +def test_ticket47653_modify(topology): + ''' + It checks that, bound as bind_entry, + - we can not modify an entry without the proper SELFDN aci. + - adding the ACI, we can modify the entry + ''' + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + topology.standalone.log.info("\n\n######################### MODIFY ######################\n") + + # entry to modify WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS + try: + topology.standalone.log.info("Try to modify %s (aci is missing)" % ENTRY_DN) + mod = [(ldap.MOD_REPLACE, 'postalCode', '9876')] + topology.standalone.modify_s(ENTRY_DN, mod) + except Exception as e: + topology.standalone.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ldap.INSUFFICIENT_ACCESS) + + + # Ok Now add the proper ACI + topology.standalone.log.info("Bind as %s and add the WRITE SELFDN aci" % DN_DM) + topology.standalone.simple_bind_s(DN_DM, PASSWORD) + + ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX + ACI_TARGETATTR = "(targetattr = *)" + ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME + ACI_ALLOW = "(version 3.0; acl \"SelfDN write\"; allow (write)" + ACI_SUBJECT = " userattr = \"member#selfDN\";)" + ACI_BODY = ACI_TARGET + ACI_TARGETATTR + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT + mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)] + topology.standalone.modify_s(SUFFIX, mod) + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # modify the entry and checks the value + topology.standalone.log.info("Try to modify %s. It should succeeds" % ENTRY_DN) + mod = [(ldap.MOD_REPLACE, 'postalCode', '1928')] + topology.standalone.modify_s(ENTRY_DN, mod) + + ents = topology.standalone.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*') + assert len(ents) == 1 + assert ents[0].postalCode == '1928' + +def test_ticket47653_delete(topology): + ''' + It checks that, bound as bind_entry, + - we can not delete an entry without the proper SELFDN aci. + - adding the ACI, we can delete the entry + ''' + topology.standalone.log.info("\n\n######################### DELETE ######################\n") + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # entry to delete WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS + try: + topology.standalone.log.info("Try to delete %s (aci is missing)" % ENTRY_DN) + topology.standalone.delete_s(ENTRY_DN) + except Exception as e: + topology.standalone.log.info("Exception (expected): %s" % type(e).__name__) + assert isinstance(e, ldap.INSUFFICIENT_ACCESS) + + # Ok Now add the proper ACI + topology.standalone.log.info("Bind as %s and add the READ/SEARCH SELFDN aci" % DN_DM) + topology.standalone.simple_bind_s(DN_DM, PASSWORD) + + ACI_TARGET = "(target = \"ldap:///cn=*,%s\")" % SUFFIX + ACI_TARGETFILTER = "(targetfilter =\"(objectClass=%s)\")" % OC_NAME + ACI_ALLOW = "(version 3.0; acl \"SelfDN delete\"; allow (delete)" + ACI_SUBJECT = " userattr = \"member#selfDN\";)" + ACI_BODY = ACI_TARGET + ACI_TARGETFILTER + ACI_ALLOW + ACI_SUBJECT + mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)] + topology.standalone.modify_s(SUFFIX, mod) + + # bind as bind_entry + topology.standalone.log.info("Bind as %s" % BIND_DN) + topology.standalone.simple_bind_s(BIND_DN, BIND_PW) + + # entry to search with the proper aci + topology.standalone.log.info("Try to delete %s should be successful" % ENTRY_DN) + topology.standalone.delete_s(ENTRY_DN) + +def test_ticket47653_final(topology): + topology.standalone.stop(timeout=10) + + + +def run_isolated(): + ''' + run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..) + To run isolated without py.test, you need to + - edit this file and comment '@pytest.fixture' line before 'topology' function. + - set the installation prefix + - run this program + ''' + global installation_prefix + installation_prefix = None + + topo = topology(True) + test_ticket47653_init(topo) + + test_ticket47653_add(topo) + test_ticket47653_search(topo) + test_ticket47653_modify(topo) + test_ticket47653_delete(topo) + + test_ticket47653_final(topo) + + +if __name__ == '__main__': + run_isolated() + diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h index cc042d6..1a05e1f 100644 --- a/ldap/servers/plugins/acl/acl.h +++ b/ldap/servers/plugins/acl/acl.h @@ -145,6 +145,7 @@ static char* const access_str_proxy = "proxy"; #define DS_LAS_GROUP "group" #define DS_LAS_USERDN "userdn" #define DS_LAS_GROUPDN "groupdn" +#define DS_LAS_SELFDNATTR "selfdnattr" #define DS_LAS_USERDNATTR "userdnattr" #define DS_LAS_AUTHMETHOD "authmethod" #define DS_LAS_GROUPDNATTR "groupdnattr" diff --git a/ldap/servers/plugins/acl/acllas.c b/ldap/servers/plugins/acl/acllas.c index 3646fcd..e2e8983 100644 --- a/ldap/servers/plugins/acl/acllas.c +++ b/ldap/servers/plugins/acl/acllas.c @@ -1388,6 +1388,261 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, /*************************************************************************** * +* DS_LASSelfDnAttrEval +* +* +* Input: +* attr_name The string "selfdn" - in lower case. +* comparator CMP_OP_EQ or CMP_OP_NE only +* attr_pattern A comma-separated list of users +* cachable Always set to FALSE. +* subject Subject property list +* resource Resource property list +* auth_info Authentication info, if any +* +* Returns: +* retcode The usual LAS return codes. +* +* Error Handling: +* None. +* +**************************************************************************/ +struct selfdnattr_info { + char *attr; + int result; + char *clientdn; + Acl_PBlock *aclpb; +}; +#define ACLLAS_MAX_LEVELS 10 +int +DS_LASSelfDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, + char *attr_pattern, int *cachable, void **LAS_cookie, + PList_t subject, PList_t resource, PList_t auth_info, + PList_t global_auth) +{ + + char *n_currEntryDn = NULL; + char *s_attrName, *attrName; + char *ptr; + int matched; + int rc, len, i; + char *val; + Slapi_Attr *a; + int levels[ACLLAS_MAX_LEVELS]; + int numOflevels =0; + struct userdnattr_info info = {0}; + char *attrs[2] = { LDAP_ALL_USER_ATTRS, NULL }; + lasInfo lasinfo; + int got_undefined = 0; + + if ( 0 != (rc = __acllas_setup (errp, attr_name, comparator, 0, /* Don't allow range comparators */ + attr_pattern,cachable,LAS_cookie, + subject, resource, auth_info,global_auth, + DS_LAS_SELFDNATTR, "DS_LASSelfDnAttrEval", + &lasinfo )) ) { + return LAS_EVAL_FAIL; + } + + /* + ** The selfdnAttr syntax is + ** selfdnattr = or + ** selfdnattr = parent[0,2,4].attribute" + ** Ex: + ** selfdnattr = manager; or + ** selfdnattr = "parent[0,2,4].manager"; + ** + ** Here 0 means current level, 2 means grandfather and + ** 4 (great great grandfather) + ** + ** The function of this LAS is to compare the value of the + ** attribute in the Slapi_Entry with the "userdn". + ** + ** Ex: userdn: "cn=prasanta, o= netscape, c= us" + ** and in the Slapi_Entry the manager attribute has + ** manager = . Compare the userdn with manager.value to + ** determine the result. + ** + */ + s_attrName = attrName = slapi_ch_strdup (attr_pattern); + + /* ignore leading/trailing whitespace */ + while(ldap_utf8isspace(attrName)) LDAP_UTF8INC(attrName); + len = strlen(attrName); + ptr = attrName+len-1; + while(ptr >= attrName && ldap_utf8isspace(ptr)) { + *ptr = '\0'; + LDAP_UTF8DEC(ptr); + } + + + /* See if we have a parent[2].attr" rule */ + if (strstr(attrName, "parent[") != NULL) { + char *word, *str, *next; + + numOflevels = 0; + n_currEntryDn = slapi_entry_get_ndn ( lasinfo.resourceEntry ); + str = attrName; + + ldap_utf8strtok_r(str, "[],. ",&next); + /* The first word is "parent[" and so it's not important */ + + while ((word= ldap_utf8strtok_r(NULL, "[],.", &next)) != NULL) { + if (ldap_utf8isdigit(word)) { + while (word && ldap_utf8isspace(word)) LDAP_UTF8INC(word); + if (numOflevels < ACLLAS_MAX_LEVELS) + levels[numOflevels++] = atoi (word); + else { + /* + * Here, ignore the extra levels..it's really + * a syntax error which should have been ruled out at parse time + */ + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASUserDnattr: Exceeded the ATTR LIMIT:%d: Ignoring extra levels\n", + ACLLAS_MAX_LEVELS); + } + } else { + /* Must be the attr name. We can goof of by + ** having parent[1,2,a] but then you have to be + ** stupid to do that. + */ + char *p = word; + if (*--p == '.') { + attrName = word; + break; + } + } + } + info.attr = attrName; + info.clientdn = lasinfo.clientDn; + info.result = 0; + } else { + levels[0] = 0; + numOflevels = 1; + + } + + /* No attribute name specified--it's a syntax error and so undefined */ + if (attrName == NULL ) { + slapi_ch_free ( (void**) &s_attrName); + return LAS_EVAL_FAIL; + } + + slapi_log_error( SLAPI_LOG_ACL, plugin_name,"Attr:%s\n" , attrName); + matched = ACL_FALSE; + for (i=0; i < numOflevels; i++) { + if ( levels[i] == 0 ) { + Slapi_Value *sval=NULL; + const struct berval *attrVal; + int j; + + slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &a); + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASSelfDnAttrEval: retrieve %s in resource\n", + attrName ); + if ( NULL == a ) continue; + j= slapi_attr_first_value ( a,&sval ); + while ( j != -1 ) { + attrVal = slapi_value_get_berval ( sval ); + /* Here if atleast 1 value matches then we are done.*/ + val = slapi_create_dn_string("%s", attrVal->bv_val); + if (NULL == val) { + slapi_log_error( SLAPI_LOG_FATAL, plugin_name, + "DS_LASSelfDnAttrEval: Invalid syntax: %s\n", + attrVal->bv_val ); + slapi_ch_free ( (void**) &s_attrName); + return LAS_EVAL_FAIL; + } + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "DS_LASSelfDnAttrEval: testing %s against bound DN %s\n", + val, lasinfo.clientDn ); + if (slapi_utf8casecmp((ACLUCHP)val, (ACLUCHP)lasinfo.clientDn ) == 0) { + char ebuf [ BUFSIZ ]; + /* Wow it matches */ + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "selfdnAttr matches(%s, %s) level (%d)\n", + val, + ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo.clientDn, ebuf), + 0); + matched = ACL_TRUE; + slapi_ch_free ( (void **) &val); + break; + } + slapi_ch_free ( (void**) &val); + j = slapi_attr_next_value ( a, j, &sval ); + } + } else { + char *p_dn; /* parent dn */ + + p_dn = acllas__dn_parent (n_currEntryDn, levels[i]); + if (p_dn == NULL) continue; + + /* use new search internal API */ + { + Slapi_PBlock *aPb = slapi_pblock_new (); + + /* + * This search may be chained if chaining for ACL is + * is enabled in the backend and the entry is in + * a chained backend. + */ + slapi_search_internal_set_pb ( aPb, + p_dn, + LDAP_SCOPE_BASE, + "objectclass=*", + &attrs[0], + 0, + NULL /* controls */, + NULL /* uniqueid */, + aclplugin_get_identity (ACL_PLUGIN_IDENTITY), + 0 /* actions */); + + slapi_search_internal_callback_pb(aPb, + &info /* callback_data */, + NULL/* result_callback */, + acllas__verify_client, + NULL /* referral_callback */); + slapi_pblock_destroy(aPb); + } + + /* + * Currently info.result is boolean so + * we do not need to check for ACL_DONT_KNOW + */ + if (info.result) { + matched = ACL_TRUE; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "selfdnAttr matches at level (%d)\n", levels[i]); + } + } + if (matched == ACL_TRUE) { + break; + } + } + + slapi_ch_free ( (void **) &s_attrName); + + /* + * If no terms were undefined, then evaluate as normal. + * If there was an undefined term, but another one was TRUE, then we also evaluate + * as normal. Otherwise, the whole expression is UNDEFINED. + */ + if ( matched == ACL_TRUE || !got_undefined ) { + if (comparator == CMP_OP_EQ) { + rc = (matched == ACL_TRUE ? LAS_EVAL_TRUE : LAS_EVAL_FALSE); + } else { + rc = (matched == ACL_TRUE ? LAS_EVAL_FALSE : LAS_EVAL_TRUE); + } + } else { + rc = LAS_EVAL_FAIL; + slapi_log_error( SLAPI_LOG_ACL, plugin_name, + "Returning UNDEFINED for selfdnattr evaluation.\n"); + } + + return rc; +} + +/*************************************************************************** +* * DS_LASLdapUrlAttrEval * * @@ -3385,7 +3640,12 @@ DS_LASUserAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator, attrName, cachable, LAS_cookie, subject, resource, auth_info, global_auth); goto done_las; - } + } else if (0 == strncasecmp ( attrValue, "SELFDN", 6)) { + matched = DS_LASSelfDnAttrEval (errp,DS_LAS_SELFDNATTR, comparator, + attrName, cachable, LAS_cookie, + subject, resource, auth_info, global_auth); + goto done_las; + } if ( lasinfo.aclpb && ( NULL == lasinfo.aclpb->aclpb_client_entry )) { /* SD 00/16/03 pass NULL in case the req is chained */ -- 1.7.11.7