[CalendarServer-changes] [14892] CalDAVTester/trunk/odsetup.py

source_changes at macosforge.org source_changes at macosforge.org
Tue Jun 9 12:41:02 PDT 2015


Revision: 14892
          http://trac.calendarserver.org//changeset/14892
Author:   cdaboo at apple.com
Date:     2015-06-09 12:41:02 -0700 (Tue, 09 Jun 2015)
Log Message:
-----------
Now uses OD.framework. Tests for unwanted OD binds. Use ConfigRoot from actual config rather than hard-coded path.

Modified Paths:
--------------
    CalDAVTester/trunk/odsetup.py

Modified: CalDAVTester/trunk/odsetup.py
===================================================================
--- CalDAVTester/trunk/odsetup.py	2015-06-09 00:42:41 UTC (rev 14891)
+++ CalDAVTester/trunk/odsetup.py	2015-06-09 19:41:02 UTC (rev 14892)
@@ -31,9 +31,35 @@
 import uuid
 import xml.parsers.expat
 
+"""
+OpenDirectory.framework
+"""
+
+import objc as _objc
+
+__bundle__ = _objc.initFrameworkWrapper(
+    "OpenDirectory",
+    frameworkIdentifier="com.apple.OpenDirectory",
+    frameworkPath=_objc.pathForFramework(
+        "/System/Library/Frameworks/OpenDirectory.framework"
+    ),
+    globals=globals()
+)
+
+# DS attributes we need
+kDSStdRecordTypeUsers = "dsRecTypeStandard:Users"
+kDSStdRecordTypeGroups = "dsRecTypeStandard:Groups"
+kDSStdRecordTypePlaces = "dsRecTypeStandard:Places"
+kDSStdRecordTypeResources = "dsRecTypeStandard:Resources"
+
+kDS1AttrGeneratedUID = "dsAttrTypeStandard:GeneratedUID"
+kDSNAttrRecordName = "dsAttrTypeStandard:RecordName"
+
+eDSExact = 0x2001
+
+
 sys_root = "/Applications/Server.app/Contents/ServerRoot"
 os.environ["PATH"] = "%s/usr/bin:%s" % (sys_root, os.environ["PATH"])
-conf_root = "/Library/Server/Calendar and Contacts/Config"
 
 diradmin_user = "admin"
 diradmin_pswd = ""
@@ -120,9 +146,9 @@
 }
 
 i18nattrs = {
-    "dsAttrTypeStandard:RealName": "まだ",
-    "dsAttrTypeStandard:FirstName": "ま",
-    "dsAttrTypeStandard:LastName": "だ",
+    "dsAttrTypeStandard:RealName": u"まだ",
+    "dsAttrTypeStandard:FirstName": u"ま",
+    "dsAttrTypeStandard:LastName": u"だ",
     "dsAttrTypeStandard:EMailAddress": "i18nuser at example.com",
 }
 
@@ -232,15 +258,15 @@
 }
 
 records = (
-    ("/Users", "testadmin", "testadmin", adminattrs, 1),
-    ("/Users", "apprentice", "apprentice", apprenticeattrs, 1),
-    ("/Users", "i18nuser", "i18nuser", i18nattrs, 1),
-    ("/Users", "user%02d", "user%02d", userattrs, None),
-    ("/Users", "public%02d", "public%02d", publicattrs, number_of_publics),
-    ("/Places", "location%02d", "location%02d", locationattrs, number_of_locations),
-    ("/Places", "delegatedroom", "delegatedroom", delegatedroomattrs, 1),
-    ("/Resources", "resource%02d", "resource%02d", resourceattrs, number_of_resources),
-    ("/Groups", "group%02d", "group%02d", groupattrs, number_of_groups),
+    (kDSStdRecordTypeUsers, "testadmin", "testadmin", adminattrs, 1),
+    (kDSStdRecordTypeUsers, "apprentice", "apprentice", apprenticeattrs, 1),
+    (kDSStdRecordTypeUsers, "i18nuser", "i18nuser", i18nattrs, 1),
+    (kDSStdRecordTypeUsers, "user%02d", "user%02d", userattrs, None),
+    (kDSStdRecordTypeUsers, "public%02d", "public%02d", publicattrs, number_of_publics),
+    (kDSStdRecordTypePlaces, "location%02d", "location%02d", locationattrs, number_of_locations),
+    (kDSStdRecordTypePlaces, "delegatedroom", "delegatedroom", delegatedroomattrs, 1),
+    (kDSStdRecordTypeResources, "resource%02d", "resource%02d", resourceattrs, number_of_resources),
+    (kDSStdRecordTypeGroups, "group%02d", "group%02d", groupattrs, number_of_groups),
 )
 
 def usage():
@@ -251,6 +277,7 @@
     -u uid    OpenDirectory Admin user id
     -p pswd   OpenDirectory Admin user password
     -c users  number of user accounts to create (default: 10)
+    -x        disable OD node checks
     -v        verbose logging
     -V        very verbose logging
 """
@@ -300,6 +327,83 @@
 
 
 
+class ODError(Exception):
+    pass
+
+
+
+class ODFamework(object):
+
+    def __init__(self, nodeName, user, pswd):
+        self.session = ODSession.defaultSession()
+        self.node, error = ODNode.nodeWithSession_name_error_(self.session, nodeName, None)
+        if error:
+            print(error)
+            raise ODError(error)
+
+        _ignore_result, error = self.node.setCredentialsWithRecordType_recordName_password_error_(
+            kDSStdRecordTypeUsers,
+            user,
+            pswd,
+            None
+        )
+        if error:
+            print("Unable to authenticate with directory %s: %s" % (nodeName, error))
+            raise ODError(error)
+
+        print("Successfully authenticated with directory %s" % (nodeName,))
+
+
+    def lookupRecordName(self, recordType, name):
+        query, error = ODQuery.queryWithNode_forRecordTypes_attribute_matchType_queryValues_returnAttributes_maximumResults_error_(
+            self.node,
+            recordType,
+            kDSNAttrRecordName,
+            eDSExact,
+            name,
+            [kDS1AttrGeneratedUID],
+            0,
+            None)
+        if error:
+            raise ODError(error)
+        records, error = query.resultsAllowingPartial_error_(False, None)
+        if error:
+            raise ODError(error)
+
+        if len(records) < 1:
+            return None
+        if len(records) > 1:
+            raise ODError("Multiple records for '%s' were found" % (name,))
+
+        return records[0]
+
+
+    def createRecord(self, recordType, recordName, password, attrs):
+        record, error = self.node.createRecordWithRecordType_name_attributes_error_(
+            recordType,
+            recordName,
+            attrs,
+            None)
+        if error:
+            print(error)
+            raise ODError(error)
+        if recordType == kDSStdRecordTypeUsers:
+            _ignore_result, error = record.changePassword_toPassword_error_(None, password, None)
+            if error:
+                print(error)
+                raise ODError(error)
+        return record
+
+
+    def recordDetails(self, record):
+        details, error = record.recordDetailsForAttributes_error_(None, None)
+        if error:
+            print(error)
+            raise ODError(error)
+        return details
+
+
+
 def readConfig():
     """
     Read useful information from calendarserver_config
@@ -308,6 +412,7 @@
     args = [
         configutility,
         "ServerHostName",
+        "ConfigRoot",
         "DocumentRoot",
         "HTTPPort",
         "SSLPort",
@@ -339,11 +444,12 @@
         int(currentConfig["SSLPort"]),
         authtype,
         currentConfig["DocumentRoot"],
+        currentConfig["ConfigRoot"],
     )
 
 
 
-def patchConfig(admin):
+def patchConfig(confroot, admin):
     """
     Patch the caldavd-user.plist file to make sure:
        * the proper admin principal is configured
@@ -388,7 +494,7 @@
         "lockRescheduleInterval": 1,
     }
 
-    writePlist(plist, conf_root + "/caldavd-user.plist")
+    writePlist(plist, confroot + "/caldavd-user.plist")
 
 
 
@@ -447,9 +553,9 @@
 
 
 def loadLists(path, records):
-    if path == "/Places":
+    if path == kDSStdRecordTypePlaces:
         result = cmd(cmdutility, locationlistcmd)
-    elif path == "/Resources":
+    elif path == kDSStdRecordTypeResources:
         result = cmd(cmdutility, resourcelistcmd)
     else:
         raise ValueError()
@@ -465,10 +571,10 @@
 
 
 
-def doToAccounts(protocol, f, users_only=False):
+def doToAccounts(odf, protocol, f, users_only=False):
 
     for record in records:
-        if protocol == "carddav" and record[0] in ("/Places", "/Resources"):
+        if protocol == "carddav" and record[0] in (kDSStdRecordTypePlaces, kDSStdRecordTypeResources):
             continue
         if record[4] is None:
             count = number_of_users
@@ -484,13 +590,13 @@
                         value = value % (ctr,)
                     attrs[key] = value
                 ruser = (record[1] % (ctr,), record[2] % (ctr,), attrs, 1)
-                f(record[0], ruser)
+                f(odf, record[0], ruser)
         else:
-            f(record[0], record[1:])
+            f(odf, record[0], record[1:])
 
 
 
-def doGroupMemberships():
+def doGroupMemberships(odf):
 
     memberships = (
         ("group01", ("user01",), (),),
@@ -503,60 +609,72 @@
     )
 
     for groupname, users, nestedgroups in memberships:
+        if verbose:
+            print "Group membership: {}".format(groupname)
 
-        memberGUIDs = [guids[user] for user in users]
-        nestedGUIDs = [guids[group] for group in nestedgroups]
+        # Get group record
+        group = odf.lookupRecordName(kDSStdRecordTypeGroups, groupname)
+        if group is not None:
+            for user in users:
+                member = odf.lookupRecordName(kDSStdRecordTypeUsers, user)
+                if member is not None:
+                    _ignore_result, error = group.addMemberRecord_error_(member, None)
+                    if error:
+                        raise ODError(error)
+            for nested in nestedgroups:
+                member = odf.lookupRecordName(kDSStdRecordTypeGroups, nested)
+                if member is not None:
+                    _ignore_result, error = group.addMemberRecord_error_(member, None)
+                    if error:
+                        raise ODError(error)
 
-        cmd("dscl -u %s -P %s %s -append /Groups/%s \"dsAttrTypeStandard:GroupMembers\"%s" % (diradmin_user, diradmin_pswd, directory_node, groupname, "".join([" \"%s\"" % (guid,) for guid in memberGUIDs])), raiseOnFail=False)
-        cmd("dscl -u %s -P %s %s -append /Groups/%s \"dsAttrTypeStandard:NestedGroups\"%s" % (diradmin_user, diradmin_pswd, directory_node, groupname, "".join([" \"%s\"" % (guid,) for guid in nestedGUIDs])), raiseOnFail=False)
 
 
+def createUser(odf, path, user):
 
-def createUser(path, user):
+    if verbose:
+        print "Create user: {}/{}".format(path, user[0])
 
-    if path in ("/Users", "/Groups",):
-        createUserViaDS(path, user)
+    if path in (kDSStdRecordTypeUsers, kDSStdRecordTypeGroups,):
+        createUserViaDS(odf, path, user)
     elif protocol == "caldav":
         createUserViaGateway(path, user)
 
 
 
-def createUserViaDS(path, user):
+def createUserViaDS(odf, path, user):
     # Do dscl command line operations to create a calendar user
 
     # Only create if it does not exist
-    if cmd("dscl %s -list %s/%s" % (directory_node, path, user[0]), raiseOnFail=False)[1] != 0:
+    record = odf.lookupRecordName(path, user[0])
+
+    if record is None:
         # Create the user
-        cmd("dscl -u %s -P %s %s -create %s/%s" % (diradmin_user, diradmin_pswd, directory_node, path, user[0]))
+        if kDS1AttrGeneratedUID in user[2]:
+            user[2][kDS1AttrGeneratedUID] = str(uuid.uuid4()).upper()
 
-        # Set the password (only for /Users)
-        if path == "/Users":
-            cmd("dscl -u %s -P %s %s -passwd %s/%s %s" % (diradmin_user, diradmin_pswd, directory_node, path, user[0], user[1]))
-
-        # Other attributes
-        for key, value in user[2].iteritems():
-            if key == "dsAttrTypeStandard:GeneratedUID":
-                value = str(uuid.uuid4()).upper()
-            cmd("dscl -u %s -P %s %s -create %s/%s \"%s\" \"%s\"" % (diradmin_user, diradmin_pswd, directory_node, path, user[0], key, value))
+        user = (user[0], user[1], dict([(k, [v],) for k, v in user[2].items()]),)
+        record = odf.createRecord(path, user[0], user[1], user[2])
     else:
-        print "%s/%s already exists" % (path, user[0],)
+        if verbose:
+            print "%s/%s already exists" % (path, user[0],)
 
     # Now read the guid for this record
     if user[0] in guids:
-        result = cmd("dscl %s -read %s/%s GeneratedUID" % (directory_node, path, user[0]))
-        guid = result[0].split()[1]
-        guids[user[0]] = guid
+        record = odf.lookupRecordName(path, user[0])
+        details = odf.recordDetails(record)
+        guids[user[0]] = details[kDS1AttrGeneratedUID][0]
 
 
 
 def createUserViaGateway(path, user):
 
     # Check for existing
-    if path == "/Places":
+    if path == kDSStdRecordTypePlaces:
         if user[0] in locations:
             guids[user[0]] = locations[user[0]]
             return
-    elif path == "/Resources":
+    elif path == kDSStdRecordTypeResources:
         if user[0] in resources:
             guids[user[0]] = resources[user[0]]
             return
@@ -564,7 +682,7 @@
     guid = str(uuid.uuid4()).upper()
     if user[0] in guids:
         guids[user[0]] = guid
-    if path == "/Places":
+    if path == kDSStdRecordTypePlaces:
         cmd(
             cmdutility,
             locationcreatecmd % {
@@ -573,7 +691,7 @@
                 "recordname": user[0]
             }
         )
-    elif path == "/Resources":
+    elif path == kDSStdRecordTypeResources:
         cmd(
             cmdutility,
             resourcecreatecmd % {
@@ -587,26 +705,32 @@
 
 
 
-def removeUser(path, user):
+def removeUser(odf, path, user):
 
-    if path in ("/Users", "/Groups",):
-        removeUserViaDS(path, user)
+    if verbose:
+        print "Remove user: {}/{}".format(path, user[0])
+
+    if path in (kDSStdRecordTypeUsers, kDSStdRecordTypeGroups,):
+        removeUserViaDS(odf, path, user)
     else:
         removeUserViaGateway(path, user)
 
 
 
-def removeUserViaDS(path, user):
+def removeUserViaDS(odf, path, user):
     # Do dscl command line operations to remove a calendar user
 
-    # Create the user
-    cmd("dscl -u %s -P %s %s -delete %s/%s" % (diradmin_user, diradmin_pswd, directory_node, path, user[0]), raiseOnFail=False)
+    record = odf.lookupRecordName(path, user[0])
+    if record is not None:
+        _ignore_result, error = record.deleteRecordAndReturnError_(None)
+        if error:
+            raise ODError(error)
 
 
 
 def removeUserViaGateway(path, user):
 
-    if path == "/Places":
+    if path == kDSStdRecordTypePlaces:
         if user[0] not in locations:
             return
         guid = locations[user[0]]
@@ -614,7 +738,7 @@
             cmdutility,
             locationremovecmd % {"guid": guid, }
         )
-    elif path == "/Resources":
+    elif path == kDSStdRecordTypeResources:
         if user[0] not in resources:
             return
         guid = resources[user[0]]
@@ -627,14 +751,14 @@
 
 
 
-def manageRecords(path, user):
+def manageRecords(odf, path, user):
     """
     Set proxies and auto-schedule for locations and resources
     """
 
     # Do caldav_utility setup
-    if path in ("/Places", "/Resources",):
-        if path in ("/Places",):
+    if path in (kDSStdRecordTypePlaces, kDSStdRecordTypeResources,):
+        if path in (kDSStdRecordTypePlaces,):
             if user[0] == "delegatedroom":
                 cmd("%s --add-write-proxy groups:group05 --add-read-proxy groups:group07 --set-auto-schedule-mode=none locations:%s" % (
                     utility,
@@ -686,8 +810,9 @@
 
     protocol = "caldav"
     serverinfo_default = details[protocol]["serverinfo"]
+    node_check = True
     try:
-        options, args = getopt.getopt(sys.argv[1:], "hn:p:u:f:c:vV")
+        options, args = getopt.getopt(sys.argv[1:], "hn:p:u:f:c:vVx")
 
         for option, value in options:
             if option == "-h":
@@ -706,6 +831,8 @@
             elif option == "-V":
                 verbose = True
                 veryverbose = True
+            elif option == "-x":
+                node_check = False
             else:
                 print "Unrecognized option: %s" % (option,)
                 usage()
@@ -728,23 +855,26 @@
             usage()
             raise ValueError
 
-        checkDataSource(directory_node)
+        odf = ODFamework(directory_node, diradmin_user, diradmin_pswd)
 
+        if node_check:
+            checkDataSource(directory_node)
+
         if args[0] == "create":
             # Read the caldavd.plist file and extract some information we will need.
-            hostname, port, sslport, authtype, docroot = readConfig()
+            hostname, port, sslport, authtype, docroot, confroot = readConfig()
 
             # Now generate the OD accounts (caching guids as we go).
             if protocol == "caldav":
-                loadLists("/Places", locations)
-                loadLists("/Resources", resources)
+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
 
-            doToAccounts(protocol, createUser)
-            doGroupMemberships()
-            doToAccounts(protocol, manageRecords)
+            doToAccounts(odf, protocol, createUser)
+            doGroupMemberships(odf)
+            doToAccounts(odf, protocol, manageRecords)
 
             # Patch the caldavd.plist file with the testadmin user's guid-based principal-URL
-            patchConfig("/principals/__uids__/%s/" % (guids["testadmin"],))
+            patchConfig(confroot, "/principals/__uids__/%s/" % (guids["testadmin"],))
 
             # Create an appropriate serverinfo.xml file from the template
             buildServerinfo(serverinfo_default, hostname, port, sslport, authtype, docroot)
@@ -752,23 +882,23 @@
 
         elif args[0] == "create-users":
             # Read the caldavd.plist file and extract some information we will need.
-            hostname, port, sslport, authtype, docroot = readConfig()
+            hostname, port, sslport, authtype, docroot, confroot = readConfig()
 
             # Now generate the OD accounts (caching guids as we go).
             if protocol == "caldav":
-                loadLists("/Places", locations)
-                loadLists("/Resources", resources)
+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
 
-            doToAccounts(protocol, createUser, users_only=True)
+            doToAccounts(odf, protocol, createUser, users_only=True)
 
             # Create an appropriate serverinfo.xml file from the template
             buildServerinfo(serverinfo_default, hostname, port, sslport, authtype, docroot)
 
         elif args[0] == "remove":
             if protocol == "caldav":
-                loadLists("/Places", locations)
-                loadLists("/Resources", resources)
-            doToAccounts(protocol, removeUser)
+                loadLists(kDSStdRecordTypePlaces, locations)
+                loadLists(kDSStdRecordTypeResources, resources)
+            doToAccounts(odf, protocol, removeUser)
 
     except Exception, e:
         traceback.print_exc()
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20150609/2ba18a4d/attachment-0001.html>


More information about the calendarserver-changes mailing list