From 1f28ba9f4468001c102098de8d033bab08170a0a Mon Sep 17 00:00:00 2001
From: William Brown <firstyear@redhat.com>
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

