[389-commits] dirsrvtests/tickets ldap/servers

thierry bordaz tbordaz at fedoraproject.org
Fri Jan 31 18:00:18 UTC 2014


 dirsrvtests/tickets/ticket47653MMR_test.py |  552 +++++++++++++++++++++++++++++
 dirsrvtests/tickets/ticket47653_test.py    |  432 ++++++++++++++++++++++
 ldap/servers/plugins/acl/acl.h             |    1 
 ldap/servers/plugins/acl/acllas.c          |   55 ++
 4 files changed, 1030 insertions(+), 10 deletions(-)

New commits:
commit 4db4a0eb62a428891aac4241c14498170930881f
Author: Thierry bordaz (tbordaz) <tbordaz at redhat.com>
Date:   Thu Jan 30 19:09:32 2014 +0100

    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_LASUserDnAttrEval (called by DS_LASUserAttrEval).
            The syntax in the aci is :
                    <userattr> = <attribute>#SELFDN
            If the BindDN (lasinfo.clientDn) exists in entry.<attribute>
            (lasinfo.resourceEntry[attrName]), then the aci matched
    
    https://fedorahosted.org/389/ticket/47653
    
    Reviewed by: Ludwig Krispenz, Rich Megginson
    
    Platforms tested: F17/F19(jenkins)
    
    Flag Day: no
    
    Doc impact: no

diff --git a/dirsrvtests/tickets/ticket47653MMR_test.py b/dirsrvtests/tickets/ticket47653MMR_test.py
new file mode 100644
index 0000000..1f221c6
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47653MMR_test.py
@@ -0,0 +1,552 @@
+'''
+Created on Nov 7, 2013
+
+ at author: tbordaz
+'''
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import time
+import logging
+import pytest
+import re
+from lib389 import DirSrv, Entry, tools
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from constants import *
+from lib389._constants import *
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+#
+# important part. We can deploy Master1 and Master2 on different versions
+#
+installation1_prefix = None
+installation2_prefix = None
+
+TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
+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 TopologyMaster1Master2(object):
+    def __init__(self, master1, master2):
+        master1.open()
+        self.master1 = master1
+        
+        master2.open()
+        self.master2 = master2
+
+
+ at pytest.fixture(scope="module")
+def topology(request):
+    '''
+        This fixture is used to create a replicated topology for the 'module'.
+        The replicated topology is MASTER1 <-> Master2.
+        At the beginning, It may exists a master2 instance and/or a master2 instance.
+        It may also exists a backup for the master1 and/or the master2.
+    
+        Principle:
+            If master1 instance exists:
+                restart it
+            If master2 instance exists:
+                restart it
+            If backup of master1 AND backup of master2 exists:
+                create or rebind to master1
+                create or rebind to master2
+
+                restore master1 from backup
+                restore master2 from backup
+            else:
+                Cleanup everything
+                    remove instances
+                    remove backups
+                Create instances
+                Initialize replication
+                Create backups
+    '''
+    global installation1_prefix
+    global installation2_prefix
+
+    # allocate master1 on a given deployement
+    master1   = DirSrv(verbose=False)
+    if installation1_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation1_prefix
+        
+    # Args for the master1 instance
+    args_instance[SER_HOST] = HOST_MASTER_1
+    args_instance[SER_PORT] = PORT_MASTER_1
+    args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
+    args_master = args_instance.copy()
+    master1.allocate(args_master)
+    
+    # allocate master1 on a given deployement
+    master2 = DirSrv(verbose=False)
+    if installation2_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation2_prefix
+        
+    # Args for the consumer instance
+    args_instance[SER_HOST] = HOST_MASTER_2
+    args_instance[SER_PORT] = PORT_MASTER_2
+    args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
+    args_master = args_instance.copy()
+    master2.allocate(args_master)
+
+    
+    # Get the status of the backups
+    backup_master1 = master1.checkBackupFS()
+    backup_master2 = master2.checkBackupFS()
+    
+    # Get the status of the instance and restart it if it exists
+    instance_master1   = master1.exists()
+    if instance_master1:
+        master1.stop(timeout=10)
+        master1.start(timeout=10)
+        
+    instance_master2 = master2.exists()
+    if instance_master2:
+        master2.stop(timeout=10)
+        master2.start(timeout=10)
+    
+    if backup_master1 and backup_master2:
+        # The backups exist, assuming they are correct 
+        # we just re-init the instances with them
+        if not instance_master1:
+            master1.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            master1.open()
+        
+        if not instance_master2:
+            master2.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            master2.open()
+        
+        # restore master1 from backup
+        master1.stop(timeout=10)
+        master1.restoreFS(backup_master1)
+        master1.start(timeout=10)
+        
+        # restore master2 from backup
+        master2.stop(timeout=10)
+        master2.restoreFS(backup_master2)
+        master2.start(timeout=10)
+    else:
+        # We should be here only in two conditions
+        #      - This is the first time a test involve master-consumer
+        #        so we need to create everything
+        #      - Something weird happened (instance/backup destroyed)
+        #        so we discard everything and recreate all
+        
+        # Remove all the backups. So even if we have a specific backup file
+        # (e.g backup_master) we clear all backups that an instance my have created
+        if backup_master1:
+            master1.clearBackupFS()
+        if backup_master2:
+            master2.clearBackupFS()
+        
+        # Remove all the instances
+        if instance_master1:
+            master1.delete()
+        if instance_master2:
+            master2.delete()
+                        
+        # Create the instances
+        master1.create()
+        master1.open()
+        master2.create()
+        master2.open()
+    
+        # 
+        # Now prepare the Master-Consumer topology
+        #
+        # First Enable replication
+        master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_1)
+        master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER, replicaId=REPLICAID_MASTER_2)
+        
+        # Initialize the supplier->consumer
+        
+        properties = {RA_NAME:      r'meTo_$host:$port',
+                      RA_BINDDN:    defaultProperties[REPLICATION_BIND_DN],
+                      RA_BINDPW:    defaultProperties[REPLICATION_BIND_PW],
+                      RA_METHOD:    defaultProperties[REPLICATION_BIND_METHOD],
+                      RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+        repl_agreement = master1.agreement.create(suffix=SUFFIX, host=master2.host, port=master2.port, properties=properties)
+    
+        if not repl_agreement:
+            log.fatal("Fail to create a replica agreement")
+            sys.exit(1)
+            
+        log.debug("%s created" % repl_agreement)
+        
+        properties = {RA_NAME:      r'meTo_$host:$port',
+                      RA_BINDDN:    defaultProperties[REPLICATION_BIND_DN],
+                      RA_BINDPW:    defaultProperties[REPLICATION_BIND_PW],
+                      RA_METHOD:    defaultProperties[REPLICATION_BIND_METHOD],
+                      RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+        master2.agreement.create(suffix=SUFFIX, host=master1.host, port=master1.port, properties=properties)
+
+        master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
+        master1.waitForReplInit(repl_agreement)
+        
+        # Check replication is working fine
+        master1.add_s(Entry((TEST_REPL_DN, {
+                                                'objectclass': "top person".split(),
+                                                'sn': 'test_repl',
+                                                'cn': 'test_repl'})))
+        loop = 0
+        while loop <= 10:
+            try:
+                ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+                break
+            except ldap.NO_SUCH_OBJECT:
+                time.sleep(1)
+                loop += 1
+                
+        # Time to create the backups
+        master1.stop(timeout=10)
+        master1.backupfile = master1.backupFS()
+        master1.start(timeout=10)
+        
+        master2.stop(timeout=10)
+        master2.backupfile = master2.backupFS()
+        master2.start(timeout=10)
+    
+    # 
+    # Here we have two instances master and consumer
+    # with replication working. Either coming from a backup recovery
+    # or from a fresh (re)init
+    # Time to return the topology
+    return TopologyMaster1Master2(master1, master2)
+
+
+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.master1.log.info("Add %s that allows 'member' attribute" % OC_NAME)
+    new_oc = _oc_definition(2, OC_NAME, must = MUST, may  = MAY) 
+    topology.master1.addSchema('objectClasses', new_oc)
+    
+    
+    # entry used to bind with
+    topology.master1.log.info("Add %s" % BIND_DN)
+    topology.master1.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', str(128+8192))] # ACL + REPL
+    topology.master1.modify_s(DN_CONFIG, mod)
+    topology.master2.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.master1.modify_s(SUFFIX, mod)
+    topology.master2.modify_s(SUFFIX, mod)
+    
+    # add dummy entries
+    for cpt in range(MAX_OTHERS):
+        name = "%s%d" % (OTHER_NAME, cpt)
+        topology.master1.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
+                                            'objectclass': "top person".split(),
+                                            'sn': name,
+                                            'cn': name})))
+
+def test_ticket47653_add(topology):
+    '''
+        This test ADD an entry on MASTER1 where 47653 is fixed. Then it checks that entry is replicated
+        on MASTER2 (even if on MASTER2 47653 is NOT fixed). Then update on MASTER2 and check the update on MASTER1
+         
+        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.master1.log.info("\n\n######################### ADD ######################\n")
+    
+    # bind as bind_entry
+    topology.master1.log.info("Bind as %s" % BIND_DN)
+    topology.master1.simple_bind_s(BIND_DN, BIND_PW)
+    
+    # Prepare the entry with multivalued members
+    entry_with_members = Entry(ENTRY_DN)
+    entry_with_members.setValues('objectclass', 'top', 'person', 'OCticket47653')
+    entry_with_members.setValues('sn', ENTRY_NAME)
+    entry_with_members.setValues('cn', ENTRY_NAME)
+    entry_with_members.setValues('postalAddress', 'here')
+    entry_with_members.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_with_members.setValues('member', members)
+    
+    # Prepare the entry with only one member value
+    entry_with_member = Entry(ENTRY_DN)
+    entry_with_member.setValues('objectclass', 'top', 'person', 'OCticket47653')
+    entry_with_member.setValues('sn', ENTRY_NAME)
+    entry_with_member.setValues('cn', ENTRY_NAME)
+    entry_with_member.setValues('postalAddress', 'here')
+    entry_with_member.setValues('postalCode', '1234')
+    member = []
+    member.append(BIND_DN)
+    entry_with_member.setValues('member', member)
+    
+    # entry to add WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS
+    try:
+        topology.master1.log.info("Try to add Add  %s (aci is missing): %r" % (ENTRY_DN, entry_with_member))
+        
+        topology.master1.add_s(entry_with_member)
+    except Exception as e:
+        topology.master1.log.info("Exception (expected): %s" % type(e).__name__)
+        assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+    
+    
+    # Ok Now add the proper ACI
+    topology.master1.log.info("Bind as %s and add the ADD SELFDN aci" % DN_DM)
+    topology.master1.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.master1.modify_s(SUFFIX, mod)
+    
+    # bind as bind_entry
+    topology.master1.log.info("Bind as %s" % BIND_DN)
+    topology.master1.simple_bind_s(BIND_DN, BIND_PW)
+    
+    # entry to add WITHOUT member and WITH the ACI -> ldap.INSUFFICIENT_ACCESS
+    try:
+        topology.master1.log.info("Try to add Add  %s (member is missing)" % ENTRY_DN)
+        topology.master1.add_s(Entry((ENTRY_DN, {
+                                            'objectclass':      ENTRY_OC.split(),
+                                            'sn':               ENTRY_NAME,
+                                            'cn':               ENTRY_NAME,
+                                            'postalAddress':    'here',
+                                            'postalCode':       '1234'})))
+    except Exception as e:
+        topology.master1.log.info("Exception (expected): %s" % type(e).__name__)
+        assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+        
+    # entry to add WITH memberS and WITH the ACI -> ldap.INSUFFICIENT_ACCESS
+    # member should contain only one value
+    try:
+        topology.master1.log.info("Try to add Add  %s (with several member values)" % ENTRY_DN)
+        topology.master1.add_s(entry_with_members)
+    except Exception as e:
+        topology.master1.log.info("Exception (expected): %s" % type(e).__name__)
+        assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+    
+    topology.master1.log.info("Try to add Add  %s should be successful" % ENTRY_DN)
+    topology.master1.add_s(entry_with_member)
+    
+    #
+    # Now check the entry as been replicated
+    #
+    topology.master2.simple_bind_s(DN_DM, PASSWORD)
+    topology.master1.log.info("Try to retrieve %s from Master2" % ENTRY_DN)
+    loop = 0
+    while loop <= 10:
+        try:
+            ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+            break
+        except ldap.NO_SUCH_OBJECT:
+            time.sleep(1)
+            loop += 1
+    assert loop <= 10
+    
+    # Now update the entry on Master2 (as DM because 47653 is possibly not fixed on M2)
+    topology.master1.log.info("Update  %s on M2" % ENTRY_DN)
+    mod = [(ldap.MOD_REPLACE, 'description', 'test_add')]
+    topology.master2.modify_s(ENTRY_DN, mod)
+    
+    topology.master1.simple_bind_s(DN_DM, PASSWORD)
+    loop = 0
+    while loop <= 10:
+        try:
+            ent = topology.master1.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+            if ent.hasAttr('description') and (ent.getValue('description') == 'test_add'):
+                break
+        except ldap.NO_SUCH_OBJECT:
+            time.sleep(1)
+            loop += 1
+    
+    assert ent.getValue('description') == 'test_add'
+    
+def test_ticket47653_modify(topology):
+    '''
+        This test MOD an entry on MASTER1 where 47653 is fixed. Then it checks that update is replicated
+        on MASTER2 (even if on MASTER2 47653 is NOT fixed). Then update on MASTER2 (bound as BIND_DN).
+        This update may fail whether or not 47653 is fixed on MASTER2
+
+        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.master1.log.info("Bind as %s" % BIND_DN)
+    topology.master1.simple_bind_s(BIND_DN, BIND_PW)
+    
+    topology.master1.log.info("\n\n######################### MODIFY ######################\n")
+    
+    # entry to modify WITH member being BIND_DN but WITHOUT the ACI -> ldap.INSUFFICIENT_ACCESS
+    try:
+        topology.master1.log.info("Try to modify  %s (aci is missing)" % ENTRY_DN)
+        mod = [(ldap.MOD_REPLACE, 'postalCode', '9876')]
+        topology.master1.modify_s(ENTRY_DN, mod)
+    except Exception as e:
+        topology.master1.log.info("Exception (expected): %s" % type(e).__name__)
+        assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+        
+    
+    # Ok Now add the proper ACI
+    topology.master1.log.info("Bind as %s and add the WRITE SELFDN aci" % DN_DM)
+    topology.master1.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.master1.modify_s(SUFFIX, mod)
+    
+    # bind as bind_entry
+    topology.master1.log.info("M1: Bind as %s" % BIND_DN)
+    topology.master1.simple_bind_s(BIND_DN, BIND_PW)
+    
+    # modify the entry and checks the value
+    topology.master1.log.info("M1: Try to modify  %s. It should succeeds" % ENTRY_DN)
+    mod = [(ldap.MOD_REPLACE, 'postalCode', '1928')]
+    topology.master1.modify_s(ENTRY_DN, mod)
+    
+    topology.master1.log.info("M1: Bind as %s" % DN_DM)
+    topology.master1.simple_bind_s(DN_DM, PASSWORD)
+    
+    topology.master1.log.info("M1: Check the update of %s" % ENTRY_DN)
+    ents = topology.master1.search_s(ENTRY_DN, ldap.SCOPE_BASE, 'objectclass=*')
+    assert len(ents) == 1
+    assert ents[0].postalCode == '1928'
+    
+    
+    #
+    # Now check the update has been replicated on M2
+    topology.master1.log.info("M2: Bind as %s" % DN_DM)
+    topology.master2.simple_bind_s(DN_DM, PASSWORD)
+    topology.master1.log.info("M2: Try to retrieve %s" % ENTRY_DN)
+    loop = 0
+    while loop <= 10:
+        try:
+            ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+            if ent.hasAttr('postalCode') and (ent.getValue('postalCode') == '1928'):
+                break
+        except ldap.NO_SUCH_OBJECT:
+            time.sleep(1)
+            loop += 1
+    assert loop <= 10
+    assert ent.getValue('postalCode') == '1928'
+    
+    
+    # Now update the entry on Master2 bound as BIND_DN (update may fail if  47653 is  not fixed on M2)
+    topology.master1.log.info("M2: Update  %s (bound as %s)" % (ENTRY_DN, BIND_DN))
+    topology.master2.simple_bind_s(BIND_DN, PASSWORD)
+    fail = False
+    try:
+        mod = [(ldap.MOD_REPLACE, 'postalCode', '1929')]
+        topology.master2.modify_s(ENTRY_DN, mod)
+        fail = False
+    except ldap.INSUFFICIENT_ACCESS:
+        topology.master1.log.info("M2: Exception (INSUFFICIENT_ACCESS): that is fine the bug is possibly not fixed on M2")
+        fail = True
+    except Exception as e:
+        topology.master1.log.info("M2: Exception (not expected): %s" % type(e).__name__)
+        assert 0
+    
+    if not fail:
+        # Check the update has been replicaed on M1
+        topology.master1.log.info("M1: Bind as %s" % DN_DM)
+        topology.master1.simple_bind_s(DN_DM, PASSWORD)
+        topology.master1.log.info("M1: Check %s.postalCode=1929)" % (ENTRY_DN))
+        loop = 0
+        while loop <= 10:
+            try:
+                ent = topology.master1.getEntry(ENTRY_DN, ldap.SCOPE_BASE, "(objectclass=*)")
+                if ent.hasAttr('postalCode') and (ent.getValue('postalCode') == '1929'):
+                    break
+            except ldap.NO_SUCH_OBJECT:
+                time.sleep(1)
+                loop += 1
+        assert ent.getValue('postalCode') == '1929'
+        
+def test_ticket47653_final(topology):
+    topology.master1.stop(timeout=10) 
+    topology.master2.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 installation1_prefix
+    global installation2_prefix
+    installation1_prefix = None
+    installation2_prefix = None
+        
+    topo = topology(True)
+    test_ticket47653_init(topo)
+    
+    test_ticket47653_add(topo)
+    test_ticket47653_modify(topo)
+    
+    test_ticket47653_final(topo)
+    
+
+
+
+if __name__ == '__main__':
+    run_isolated()
+
diff --git a/dirsrvtests/tickets/ticket47653_test.py b/dirsrvtests/tickets/ticket47653_test.py
new file mode 100644
index 0000000..2693093
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47653_test.py
@@ -0,0 +1,432 @@
+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
+
+
+ at 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_with_members = Entry(ENTRY_DN)
+    entry_with_members.setValues('objectclass', 'top', 'person', 'OCticket47653')
+    entry_with_members.setValues('sn', ENTRY_NAME)
+    entry_with_members.setValues('cn', ENTRY_NAME)
+    entry_with_members.setValues('postalAddress', 'here')
+    entry_with_members.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_with_members.setValues('member', members)
+    
+    # Prepare the entry with one member
+    entry_with_member = Entry(ENTRY_DN)
+    entry_with_member.setValues('objectclass', 'top', 'person', 'OCticket47653')
+    entry_with_member.setValues('sn', ENTRY_NAME)
+    entry_with_member.setValues('cn', ENTRY_NAME)
+    entry_with_member.setValues('postalAddress', 'here')
+    entry_with_member.setValues('postalCode', '1234')
+    member = []
+    member.append(BIND_DN)
+    entry_with_member.setValues('member', member)
+    
+    # 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_with_member))
+        
+        topology.standalone.add_s(entry_with_member)
+    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)
+        
+    # entry to add WITH memberS and WITH the ACI -> ldap.INSUFFICIENT_ACCESS
+    # member should contain only one value
+    try:
+        topology.standalone.log.info("Try to add Add  %s (with several member values)" % ENTRY_DN)
+        topology.standalone.add_s(entry_with_members)
+    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_with_member)
+    
+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..38bc9a1 100644
--- a/ldap/servers/plugins/acl/acllas.c
+++ b/ldap/servers/plugins/acl/acllas.c
@@ -179,7 +179,7 @@
 		DS_LASRoleDnEval		- LAS Evaluation for ROLEDN		-
 			three-valued logic
 			logical combination: || and !=
-		DS_LASUserDnAttrEval	- LAS Evaluation for USERDNATTR -
+		DS_LASUserDnAttrEval	- LAS Evaluation for USERDNATTR and SELFDNATTR-
 			three-valued logic  
 			logical combination || (over specified attribute values and
 			parent keyword levels), !=
@@ -1170,11 +1170,21 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 	char			*attrs[2] = { LDAP_ALL_USER_ATTRS, NULL };
 	lasInfo			lasinfo;
 	int				got_undefined = 0;
+        PRBool selfdn;
+        
+        if (attr_name == NULL ||
+                (strcmp(DS_LAS_SELFDNATTR, attr_name) && strcmp(DS_LAS_USERDNATTR, attr_name))) {
+                slapi_log_error( SLAPI_LOG_FATAL, plugin_name, 
+                        "DS_LASUserDnattr: invalid attr_name (should be %s or %s)\n",
+                        DS_LAS_SELFDNATTR, DS_LAS_USERDNATTR);
+                return LAS_EVAL_FAIL;
+        }
+        selfdn = (strcmp(DS_LAS_SELFDNATTR, attr_name) == 0) ? PR_TRUE : PR_FALSE;
 
 	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_USERDNATTR, "DS_LASUserDnAttrEval", 
+									attr_name, "DS_LASUserDnAttrEval", 
 									&lasinfo )) ) {
 		return LAS_EVAL_FAIL;
 	}
@@ -1269,6 +1279,7 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 		if ( levels[i] == 0 ) {
 			Slapi_Value *sval=NULL;
 			const struct berval		*attrVal;
+                        int numValues = 0;
 			int j;
 
 			/*
@@ -1280,13 +1291,32 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 			*/
 
 			if ( lasinfo.aclpb->aclpb_optype == SLAPI_OPERATION_ADD) {
-				slapi_log_error( SLAPI_LOG_ACL, plugin_name,
-					"ACL info: userdnAttr does not allow ADD permission at level 0.\n");
-				got_undefined = 1;
-				continue;
+                                if (selfdn) {
+                                        slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+                                                "ACL info: %s DOES allow ADD permission at level 0.\n", attr_name);
+                                } else {
+                                        slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+                                                "ACL info: %s does not allow ADD permission at level 0.\n", attr_name);
+                                        got_undefined = 1;
+                                        continue;
+                                }
 			}
 			slapi_entry_attr_find( lasinfo.resourceEntry, attrName, &a);
 			if ( NULL == a ) continue;
+                        
+                        if (selfdn) {
+                                /* Checks that attrName has only one value. This is the only condition enforced
+                                 * when using SELFDN
+                                 */
+                                slapi_attr_get_numvalues((const Slapi_Attr *) a, &numValues);
+                                if (numValues != 1) {
+                                        slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+                                                "DS_LASSelfDnAttrEval: fail because the retrieved %s in resource has more than one value (%d)\n",
+                                                attrName, numValues);
+                                        got_undefined = 1;
+                                        continue;
+                                }
+                        }
 			j= slapi_attr_first_value ( a,&sval );
 			while ( j != -1 ) {
 				attrVal = slapi_value_get_berval ( sval );
@@ -1304,7 +1334,7 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 					char	ebuf [ BUFSIZ ];
 					/* Wow it matches */
 					slapi_log_error( SLAPI_LOG_ACL, plugin_name,
-						"userdnAttr matches(%s, %s) level (%d)\n",
+						"%s matches(%s, %s) level (%d)\n", attr_name,
 						val,
 				ACL_ESCAPE_STRING_WITH_PUNCTUATION (lasinfo.clientDn, ebuf),
 						0);
@@ -1356,7 +1386,7 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 			if (info.result) {
 				matched = ACL_TRUE;
 				slapi_log_error( SLAPI_LOG_ACL, plugin_name,
-						"userdnAttr matches at level (%d)\n", levels[i]);
+						"%s matches at level (%d)\n", attr_name, levels[i]);
 			}
 		}
 		if (matched == ACL_TRUE) {				
@@ -1380,7 +1410,7 @@ DS_LASUserDnAttrEval(NSErr_t *errp, char *attr_name, CmpOp_t comparator,
 	} else {
 		rc = LAS_EVAL_FAIL;
 		slapi_log_error( SLAPI_LOG_ACL, plugin_name, 
-			"Returning UNDEFINED for userdnattr evaluation.\n");
+			"Returning UNDEFINED for %s evaluation.\n", attr_name);
 	} 
 
 	return rc;
@@ -3385,7 +3415,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_LASUserDnAttrEval (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 */




More information about the 389-commits mailing list