From 1f28ba9f4468001c102098de8d033bab08170a0a Mon Sep 17 00:00:00 2001 From: William Brown Date: Tue, 3 Oct 2017 12:57:45 +1000 Subject: [PATCH] Ticket 102 - referral support Bug Description: We need support for smart referrals to test managedsait controls. Fix Description: Add support for referral objects and a test to demonstrate they work. https://pagure.io/lib389/issue/102 Author: wibrown Review by: ??? --- lib389/_mapped_object.py | 37 ++++++++++++++++++++++--------------- lib389/referral.py | 35 +++++++++++++++++++++++++++++++++++ lib389/tests/referral_test.py | 36 ++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 15 deletions(-) create mode 100644 lib389/referral.py create mode 100644 lib389/tests/referral_test.py diff --git a/lib389/_mapped_object.py b/lib389/_mapped_object.py index fe84cd4..f73cb25 100644 --- a/lib389/_mapped_object.py +++ b/lib389/_mapped_object.py @@ -108,6 +108,8 @@ class DSLdapObject(DSLogging): # attributes, we don't want to compare self._compare_exclude = ['entryid'] self._lint_functions = None + self._server_controls = None + self._client_controls = None def __unicode__(self): val = self._dn @@ -125,7 +127,7 @@ class DSLdapObject(DSLogging): :returns: Entry object """ - return self._instance.getEntry(self._dn) + return self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=["*"], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] def exists(self): """Check if the entry exists @@ -134,7 +136,7 @@ class DSLdapObject(DSLogging): """ try: - self._instance.search_s(self._dn, ldap.SCOPE_BASE, attrsonly=1) + self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrsonly=1, serverctrls=self._server_controls, clientctrls=self._client_controls) except ldap.NO_SUCH_OBJECT: return False @@ -146,7 +148,7 @@ class DSLdapObject(DSLogging): :returns: LDIF formatted string """ - e = self._instance.getEntry(self._dn) + e = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=["*"], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return e.__repr__() def display_attr(self, attr): @@ -222,7 +224,7 @@ class DSLdapObject(DSLogging): raise ValueError("Invalid state. Cannot get presence on instance that is not ONLINE") self._log.debug("%s present(%r) %s" % (self._dn, attr, value)) - e = self._instance.getEntry(self._dn, attrlist=[attr, ]) + e = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=[attr, ], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] if value is None: return e.hasAttr(attr) else: @@ -306,7 +308,7 @@ class DSLdapObject(DSLogging): if self._batch: pass else: - return self._instance.modify_s(self._dn, [(action, key, value)]) + return self._instance.modify_ext_s(self._dn, [(action, key, value)], serverctrls=self._server_controls, clientctrls=self._client_controls) def apply_mods(self, mods): """Perform modification operation using several mods at once @@ -340,7 +342,7 @@ class DSLdapObject(DSLogging): value = [ensure_bytes(value)] mod_list.append((action, key, value)) - return self._instance.modify_s(self._dn, mod_list) + return self._instance.modify_ext_s(self._dn, mod_list, serverctrls=self._server_controls, clientctrls=self._client_controls) @classmethod def compare(cls, obj1, obj2): @@ -407,7 +409,7 @@ class DSLdapObject(DSLogging): raise ValueError("Invalid state. Cannot get properties on instance that is not ONLINE") else: # retrieving real(*) and operational attributes(+) - attrs_entry = self._instance.getEntry(self._dn, ldap.SCOPE_BASE, "(objectclass=*)", ["*", "+"]) + attrs_entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=["*", "+"], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] # getting dict from 'entry' object attrs_dict = attrs_entry.data return attrs_dict @@ -417,7 +419,7 @@ class DSLdapObject(DSLogging): if self._instance.state != DIRSRV_STATE_ONLINE: raise ValueError("Invalid state. Cannot get properties on instance that is not ONLINE") else: - entry = self._instance.search_s(self._dn, ldap.SCOPE_BASE, attrlist=keys)[0] + entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=keys, serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return entry.getValuesSet(keys) def get_attr_vals(self, key): @@ -430,7 +432,7 @@ class DSLdapObject(DSLogging): else: # It would be good to prevent the entry code intercepting this .... # We have to do this in this method, because else we ignore the scope base. - entry = self._instance.search_s(self._dn, ldap.SCOPE_BASE, attrlist=[key])[0] + entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=[key], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return entry.getValues(key) def get_attr_val(self, key): @@ -441,7 +443,7 @@ class DSLdapObject(DSLogging): # In the future, I plan to add a mode where if local == true, we # can use get on dse.ldif to get values offline. else: - entry = self._instance.search_s(self._dn, ldap.SCOPE_BASE, attrlist=[key])[0] + entry = self._instance.search_ext_s(self._dn, ldap.SCOPE_BASE, attrlist=[key], serverctrls=self._server_controls, clientctrls=self._client_controls)[0] return entry.getValue(key) def get_attr_val_bytes(self, key): @@ -544,7 +546,7 @@ class DSLdapObject(DSLogging): self._log.debug("%s delete" % (self._dn)) if not self._protected: # Is there a way to mark this as offline and kill it - self._instance.delete_s(self._dn) + self._instance.delete_ext_s(self._dn, serverctrls=self._server_controls, clientctrls=self._client_controls) def _validate(self, rdn, properties, basedn): """Used to validate a create request. @@ -637,7 +639,7 @@ class DSLdapObject(DSLogging): e.update(valid_props) # We rely on exceptions here to indicate failure to the parent. self._log.debug('Creating entry %s : %s' % (dn, e)) - self._instance.add_s(e) + self._instance.add_ext_s(e, serverctrls=self._server_controls, clientctrls=self._client_controls) # If it worked, we need to fix our instance dn self._dn = dn return self @@ -694,6 +696,8 @@ class DSLdapObjects(DSLogging): self._basedn = "" self._batch = batch self._scope = ldap.SCOPE_SUBTREE + self._server_controls = None + self._client_controls = None def _get_objectclass_filter(self): return _gen_and( @@ -720,11 +724,12 @@ class DSLdapObjects(DSLogging): filterstr = self._get_objectclass_filter() self._log.debug('list filter = %s' % filterstr) try: - results = self._instance.search_s( + results = self._instance.search_ext_s( base=self._basedn, scope=self._scope, filterstr=filterstr, attrlist=self._list_attrlist, + serverctrls=self._server_controls, clientctrls=self._client_controls ) # def __init__(self, instance, dn=None, batch=False): insts = [self._entry_to_instance(dn=r.dn, entry=r) for r in results] @@ -761,11 +766,12 @@ class DSLdapObjects(DSLogging): # This will yield and & filter for objectClass with as many terms as needed. filterstr = self._get_objectclass_filter() self._log.debug('_gen_dn filter = %s' % filterstr) - return self._instance.search_s( + return self._instance.search_ext_s( base=dn, scope=ldap.SCOPE_BASE, filterstr=filterstr, attrlist=self._list_attrlist, + serverctrls=self._server_controls, clientctrls=self._client_controls ) def _get_selector(self, selector): @@ -781,11 +787,12 @@ class DSLdapObjects(DSLogging): ), ]) self._log.debug('_gen_selector filter = %s' % filterstr) - return self._instance.search_s( + return self._instance.search_ext_s( base=self._basedn, scope=self._scope, filterstr=filterstr, attrlist=self._list_attrlist, + serverctrls=self._server_controls, clientctrls=self._client_controls ) def _validate(self, rdn, properties): diff --git a/lib389/referral.py b/lib389/referral.py new file mode 100644 index 0000000..e4500cb --- /dev/null +++ b/lib389/referral.py @@ -0,0 +1,35 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2017 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import ldap + +from lib389._mapped_object import DSLdapObjects, DSLdapObject + + +class Referral(DSLdapObject): + def __init__(self, instance, dn=None, batch=False): + super(Referral, self).__init__(instance, dn, batch) + self._rdn_attribute = "cn" + self._must_attributes = ["ref"] + self._create_objectclasses = ['referral', 'nsContainer'] + self._protected = False + managedsait_ctrl = ldap.controls.simple.ManageDSAITControl() + self._server_controls = [managedsait_ctrl] + self._client_controls = None + +class Referrals(DSLdapObjects): + def __init__(self, instance, basedn, batch=False): + super(Referrals, self).__init__(instance, batch) + self._objectclasses = ['referral'] + self._filterattrs = 'cn' + self._childobject = Referral + self._basedn = basedn + managedsait_ctrl = ldap.controls.simple.ManageDSAITControl() + self._server_controls = [managedsait_ctrl] + self._client_controls = None + diff --git a/lib389/tests/referral_test.py b/lib389/tests/referral_test.py new file mode 100644 index 0000000..2e54ab0 --- /dev/null +++ b/lib389/tests/referral_test.py @@ -0,0 +1,36 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2017 Red Hat, Inc. +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- +# + +import ldap +import logging + +from lib389.referral import Referrals, Referral + +from lib389.topologies import topology_st + +from lib389._constants import DEFAULT_SUFFIX + +log = logging.getLogger(__name__) + +def test_referral(topology_st): + standalone = topology_st.standalone + + rs = Referrals(standalone, DEFAULT_SUFFIX) + + r = rs.create(properties={ + 'cn': 'testref', + 'ref': 'ldap://localhost:38901/ou=People,dc=example,dc=com' + }) + + r_all = rs.list() + assert(len(r_all) == 1) + r2 = r_all[0] + assert(r2.present('ref', 'ldap://localhost:38901/ou=People,dc=example,dc=com')) + + -- 1.8.3.1