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(a)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
+
+@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
+
+
+(a)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 '(a)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
+
+
+(a)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 '(a)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 */