From be14f69db1478594a1bc3925c3d22081c8ee9e72 Mon Sep 17 00:00:00 2001
From: William Brown <wililam@adelaide.edu.au>
Date: Mon, 3 Aug 2015 11:40:41 +0930
Subject: [PATCH] Add objectclass and attribute type query mechanisms

---
 lib389/schema.py     | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 tests/schema_test.py | 50 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 114 insertions(+)
 create mode 100644 tests/schema_test.py

diff --git a/lib389/schema.py b/lib389/schema.py
index bc17cf5..4dc9634 100644
--- a/lib389/schema.py
+++ b/lib389/schema.py
@@ -3,6 +3,7 @@
    DirSrv.schema.methodName()
 """
 import ldap
+from ldap.schema.models import AttributeType, ObjectClass
 import os
 import re
 import time
@@ -83,3 +84,66 @@ class Schema(object):
         ents = self.conn.search_s(DN_SCHEMA, ldap.SCOPE_BASE, "objectclass=*", ['nsSchemaCSN'])
         ent = ents[0]
         return ent.getValue('nsSchemaCSN')
+
+    def get_objectclasses(self):
+        """Returns a list of ldap.schema.models.ObjectClass objectsfor all objectClasses supported by this instance.
+        """
+        attrs = ['objectClasses']
+        results = self.conn.search_s(DN_SCHEMA, ldap.SCOPE_BASE, 'objectclass=*', attrs)[0]
+        objectclasses = map(lambda oc: ObjectClass(oc), results.getValues('objectClasses'))
+        return objectclasses
+
+    def get_attributetypes(self):
+        """Returns a list of ldap.schema.models.AttributeType objects for all attributeTypes supported by this instance.
+        """
+        attrs = ['attributeTypes']
+        results = self.conn.search_s(DN_SCHEMA, ldap.SCOPE_BASE, 'objectclass=*', attrs)[0]
+        attributetypes = map(lambda at: AttributeType(at), results.getValues('attributeTypes'))
+        return attributetypes
+
+    def query_objectclass(self, objectclassname):
+        """Returns a single ObjectClass instance that matches objectclassname. Returns None if the objectClass doesn't exist.
+
+        @param objectclassname - The name of the objectClass you want to query.
+
+        return ObjectClass or None
+
+        ex. query_objectclass('account')
+        <ldap.schema.models.ObjectClass instance>
+        """
+        objectclasses = self.get_objectclasses()
+        objectclass = filter(lambda oc: objectclassname in oc.names, objectclasses)
+        if len(objectclass) != 1:
+            #This is an error.
+            return None
+        objectclass = objectclass[0]
+        return objectclass
+
+    def query_attributetype(self, attributetypename):
+        """Returns a tuple of the AttributeType, and what objectclasses may or must take this attributeType. Returns None if attributetype doesn't exist.
+
+        @param attributetypename - The name of the attributeType you want to query
+
+        return (AttributeType, Must, May) or None
+
+        ex. query_attributetype('uid')
+        ( <ldap.schema.models.AttributeType instance>, [<ldap.schema.models.ObjectClass instance>, ...], [<ldap.schema.models.ObjectClass instance>, ...] )
+        """
+        # First, get the attribute that matches name. We need to consider alternate names.
+        # There is no way to search this, so we have to filter our set of all attribute types.
+        objectclasses = self.get_objectclasses()
+        attributetypes = self.get_attributetypes()
+        attributetypename = attributetypename.lower()
+
+        attributetype = filter(lambda at: attributetypename in at.names, attributetypes)
+        if len(attributetype) != 1:
+            #This is an error.
+            return None
+        attributetype = attributetype[0]
+        # Get the primary name of this attribute
+        attributetypename = attributetype.names[0]
+        # Build a set if they have may.
+        may = filter(lambda oc: attributetypename in oc.may, objectclasses)
+        # Build a set if they have must.
+        must = filter(lambda oc: attributetypename in oc.must, objectclasses)
+        return (attributetype, must, may)
diff --git a/tests/schema_test.py b/tests/schema_test.py
new file mode 100644
index 0000000..b8c0d18
--- /dev/null
+++ b/tests/schema_test.py
@@ -0,0 +1,50 @@
+'''
+Created on Aug 3, 2015
+
+@author: William Brown
+'''
+from lib389._constants import *
+from lib389 import DirSrv,Entry
+
+INSTANCE_PORT     = 54321
+INSTANCE_SERVERID = 'schemainspectds'
+INSTANCE_PREFIX   = None
+
+class Test_schema():
+    def setUp(self):
+        instance = DirSrv(verbose=False)
+        instance.log.debug("Instance allocated")
+        args = {SER_HOST:          LOCALHOST,
+                SER_PORT:          INSTANCE_PORT,
+                SER_DEPLOYED_DIR:  INSTANCE_PREFIX,
+                SER_SERVERID_PROP: INSTANCE_SERVERID
+                }
+        instance.allocate(args)
+        if instance.exists():
+            instance.delete()
+        instance.create()
+        instance.open()
+        self.instance = instance
+
+    def tearDown(self):
+        if self.instance.exists():
+            self.instance.delete()
+
+    def test_schema(self):
+        must_expect = ['uidObject', 'account', 'posixAccount', 'shadowAccount']
+        may_expect = ['cosDefinition', 'inetOrgPerson', 'inetUser', 'mailRecipient']
+        attrtype, must, may = self.instance.schema.query_attributetype('uid')
+        assert attrtype.names == ('uid', 'userid')
+        for oc in must:
+            assert oc.names[0] in must_expect
+        for oc in may:
+            assert oc.names[0] in may_expect
+        assert self.instance.schema.query_objectclass('account').names == ('account', )
+
+if __name__ == "__main__":
+    test = Test_schema()
+    test.setUp()
+    test.test_schema()
+    test.tearDown()
+
+
-- 
2.4.3

