[CalendarServer-changes] [9741] CalendarServer/trunk/twistedcaldav/directory

source_changes at macosforge.org source_changes at macosforge.org
Thu Aug 23 13:10:37 PDT 2012


Revision: 9741
          http://trac.macosforge.org/projects/calendarserver/changeset/9741
Author:   sagen at apple.com
Date:     2012-08-23 13:10:35 -0700 (Thu, 23 Aug 2012)
Log Message:
-----------
Fix the LDAP implementation of restrictToGroup

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py

Modified: CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2012-08-23 01:59:19 UTC (rev 9740)
+++ CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2012-08-23 20:10:35 UTC (rev 9741)
@@ -322,12 +322,7 @@
         for dn, attrs in results:
             dn = normalizeDNstr(dn)
 
-            unrestricted = True
-            if self.restrictedGUIDs is not None:
-                if guidAttr:
-                    guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-                    if guid not in self.restrictedGUIDs:
-                        unrestricted = False
+            unrestricted = self.isAllowedByRestrictToGroup(dn, attrs)
 
             try:
                 record = self._ldapResultToRecord(dn, attrs, recordType)
@@ -337,7 +332,7 @@
                 continue
 
             if not unrestricted:
-                self.log_debug("%s is not enabled because it's not a member of group: %s" % (guid, self.restrictToGroup))
+                self.log_debug("%s is not enabled because it's not a member of group: %s" % (dn, self.restrictToGroup))
                 record.enabledForCalendaring = False
                 record.enabledForAddressBooks = False
 
@@ -554,75 +549,132 @@
         raise HTTPError(StatusResponse(responsecode.SERVICE_UNAVAILABLE, "LDAP server unavailable"))
 
 
+    def isAllowedByRestrictToGroup(self, dn, attrs):
+        """
+        Check to see if the principal with the given DN and LDAP attributes is
+        a member of the restrictToGroup.
+
+        @param dn: an LDAP dn
+        @type dn: C{str}
+        @param attrs: LDAP attributes
+        @type attrs: C{dict}
+        @return: True if principal is in the group (or restrictEnabledRecords if turned off).
+        @rtype: C{boolean}
+        """
+        if not self.restrictEnabledRecords:
+            return True
+        if self.groupSchema["memberIdAttr"]:
+            value = self._getUniqueLdapAttribute(attrs, self.groupSchema["memberIdAttr"])
+        else: # No memberIdAttr implies DN
+            value = dn
+        return value in self.restrictedPrincipals
+
+
     @property
-    def restrictedGUIDs(self):
+    def restrictedPrincipals(self):
         """
         Look up (and cache) the set of guids that are members of the
         restrictToGroup.  If restrictToGroup is not set, return None to
         indicate there are no group restrictions.
-
-        guidAttr must also be specified in config for restrictToGroups to work.
         """
-        if self.restrictEnabledRecords and self.rdnSchema["guidAttr"]:
+        if self.restrictEnabledRecords:
 
             if time.time() - self.restrictedTimestamp > self.cacheTimeout:
                 # fault in the members of group of name self.restrictToGroup
-
                 recordType = self.recordType_groups
                 base = self.typeDNs[recordType]
+                # TODO: This shouldn't be hardcoded to cn
                 filterstr = "(cn=%s)" % (self.restrictToGroup,)
                 self.log_debug("Retrieving ldap record with base %s and filter %s." %
                     (ldap.dn.dn2str(base), filterstr))
                 result = self.timedSearch(ldap.dn.dn2str(base),
                     ldap.SCOPE_SUBTREE, filterstr=filterstr, attrlist=self.attrlist)
 
+                members = []
+                nestedGroups = []
+
                 if len(result) == 1:
                     dn, attrs = result[0]
                     dn = normalizeDNstr(dn)
                     if self.groupSchema["membersAttr"]:
-                        members = set(self._getMultipleLdapAttributes(attrs,
-                            self.groupSchema["membersAttr"]))
+                        members = self._getMultipleLdapAttributes(attrs,
+                            self.groupSchema["membersAttr"])
+                        if not self.groupSchema["memberIdAttr"]: # these are DNs
+                            members = [normalizeDNstr(m) for m in members]
+                        members = set(members)
+
                     if self.groupSchema["nestedGroupsAttr"]:
-                        nestedGroups = set(self._getMultipleLdapAttributes(attrs,
-                            self.groupSchema["nestedGroupsAttr"]))
+                        nestedGroups = self._getMultipleLdapAttributes(attrs,
+                            self.groupSchema["nestedGroupsAttr"])
+                        if not self.groupSchema["memberIdAttr"]: # these are DNs
+                            nestedGroups = [normalizeDNstr(g) for g in nestedGroups]
+                        nestedGroups = set(nestedGroups)
+                    else:
+                        # Since all members are lumped into the same attribute,
+                        # treat them all as nestedGroups instead
+                        nestedGroups = members
+                        members = set()
 
-                else:
-                    members = []
-                    nestedGroups = []
-
-                self._cachedRestrictedGUIDs = set(self._expandGroupMembership(members, nestedGroups, returnGroups=True))
-                self.log_info("Got %d restricted group members" % (len(self._cachedRestrictedGUIDs),))
+                self._cachedRestrictedPrincipals = set(self._expandGroupMembership(members,
+                    nestedGroups))
+                self.log_info("Got %d restricted group members" % (
+                    len(self._cachedRestrictedPrincipals),))
                 self.restrictedTimestamp = time.time()
-            return self._cachedRestrictedGUIDs
+            return self._cachedRestrictedPrincipals
         else:
             # No restrictions
             return None
 
 
-    def _expandGroupMembership(self, members, nestedGroups,
-        processedGUIDs=None, returnGroups=False):
+    def _expandGroupMembership(self, members, nestedGroups, processedItems=None):
+        """
+        A generator which recursively yields principals which are included within nestedGroups
 
-        if processedGUIDs is None:
-            processedGUIDs = set()
+        @param members:  If the LDAP service is configured to use different attributes to
+            indicate member users and member nested groups, members will include the non-groups.
+            Otherwise, members will be empty and only nestedGroups will be used.
+        @type members: C{set}
+        @param nestedGroups:  If the LDAP service is configured to use different attributes to
+            indicate member users and member nested groups, nestedGroups will include only
+            the groups; otherwise nestedGroups will include all members
+        @type members: C{set}
+        @param processedItems: The set of members that have already been looked up in LDAP
+            so the code doesn't have to look up the same member twice or get stuck in a
+            membership loop.
+        @type processedItems: C{set}
+        @return: All members of the group, the values will correspond to memberIdAttr
+            if memberIdAttr is set in the group schema, or DNs otherwise.
+        @rtype: generator of C{str}
+        """
 
+        if processedItems is None:
+            processedItems = set()
+
         if isinstance(members, str):
             members = [members]
 
         if isinstance(nestedGroups, str):
             nestedGroups = [nestedGroups]
 
-        for memberGUID in members:
-            if memberGUID not in processedGUIDs:
-                processedGUIDs.add(memberGUID)
-                yield memberGUID
+        for member in members:
+            if member not in processedItems:
+                processedItems.add(member)
+                yield member
 
-        for groupGUID in nestedGroups:
-            if groupGUID in processedGUIDs:
+        for group in nestedGroups:
+            if group in processedItems:
                 continue
 
             recordType = self.recordType_groups
             base = self.typeDNs[recordType]
-            filterstr = "(%s=%s)" % (self.rdnSchema["guidAttr"], groupGUID)
+            if self.groupSchema["memberIdAttr"]:
+                scope = ldap.SCOPE_SUBTREE
+                base = self.typeDNs[recordType]
+                filterstr = "(%s=%s)" % (self.groupSchema["memberIdAttr"], group)
+            else: # Use DN
+                scope = ldap.SCOPE_BASE
+                base = ldap.dn.str2dn(group)
+                filterstr = "(objectClass=*)"
 
             self.log_debug("Retrieving ldap record with base %s and filter %s." %
                 (ldap.dn.dn2str(base), filterstr))
@@ -632,28 +684,31 @@
             if len(result) == 0:
                 continue
 
+            subMembers = set()
+            subNestedGroups = set()
             if len(result) == 1:
                 dn, attrs = result[0]
                 dn = normalizeDNstr(dn)
                 if self.groupSchema["membersAttr"]:
-                    subMembers = set(self._getMultipleLdapAttributes(attrs,
-                        self.groupSchema["membersAttr"]))
-                else:
-                    subMembers = []
+                    subMembers = self._getMultipleLdapAttributes(attrs,
+                        self.groupSchema["membersAttr"])
+                    if not self.groupSchema["memberIdAttr"]: # these are DNs
+                        subMembers = [normalizeDNstr(m) for m in subMembers]
+                    subMembers = set(subMembers)
 
                 if self.groupSchema["nestedGroupsAttr"]:
-                    subNestedGroups = set(self._getMultipleLdapAttributes(attrs,
-                        self.groupSchema["nestedGroupsAttr"]))
-                else:
-                    subNestedGroups = []
+                    subNestedGroups = self._getMultipleLdapAttributes(attrs,
+                        self.groupSchema["nestedGroupsAttr"])
+                    if not self.groupSchema["memberIdAttr"]: # these are DNs
+                        subNestedGroups = [normalizeDNstr(g) for g in subNestedGroups]
+                    subNestedGroups = set(subNestedGroups)
 
-            processedGUIDs.add(groupGUID)
-            if returnGroups:
-                yield groupGUID
+            processedItems.add(group)
+            yield group
 
-            for GUID in self._expandGroupMembership(subMembers,
-                subNestedGroups, processedGUIDs, returnGroups):
-                yield GUID
+            for item in self._expandGroupMembership(subMembers, subNestedGroups,
+                processedItems):
+                yield item
 
 
     def _getUniqueLdapAttribute(self, attrs, *keys):
@@ -963,19 +1018,14 @@
                 dn, attrs = result.pop()
                 dn = normalizeDNstr(dn)
 
-                unrestricted = True
-                if self.restrictedGUIDs is not None:
-                    if guidAttr:
-                        guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-                        if guid not in self.restrictedGUIDs:
-                            unrestricted = False
+                unrestricted = self.isAllowedByRestrictToGroup(dn, attrs)
 
                 try:
                     record = self._ldapResultToRecord(dn, attrs, recordType)
                     self.log_debug("Got LDAP record %s" % (record,))
 
                     if not unrestricted:
-                        self.log_debug("%s is not enabled because it's not a member of group: %s" % (guid, self.restrictToGroup))
+                        self.log_debug("%s is not enabled because it's not a member of group: %s" % (dn, self.restrictToGroup))
                         record.enabledForCalendaring = False
                         record.enabledForAddressBooks = False
 
@@ -1045,11 +1095,8 @@
                     # Skip if group restriction is in place and guid is not
                     # a member
                     if (recordType != self.recordType_groups and
-                        self.restrictedGUIDs is not None):
-                        if guidAttr:
-                            guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-                            if guid not in self.restrictedGUIDs:
-                                continue
+                        not self.isAllowedByRestrictToGroup(dn, attrs)):
+                        continue
 
                     try:
                         record = self._ldapResultToRecord(dn, attrs, recordType)
@@ -1138,11 +1185,8 @@
                     # Skip if group restriction is in place and guid is not
                     # a member
                     if (recordType != self.recordType_groups and
-                        self.restrictedGUIDs is not None):
-                        if guidAttr:
-                            guid = self._getUniqueLdapAttribute(attrs, guidAttr)
-                            if guid not in self.restrictedGUIDs:
-                                continue
+                        not self.isAllowedByRestrictToGroup(dn, attrs)):
+                        continue
 
                     try:
                         record = self._ldapResultToRecord(dn, attrs, recordType)

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2012-08-23 01:59:19 UTC (rev 9740)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2012-08-23 20:10:35 UTC (rev 9741)
@@ -296,23 +296,58 @@
         """
 
 
-        def __init__(self, actual):
+        def __init__(self, actual, records):
             self.actual = actual
             self.async = StubAsync()
 
             # Test data returned from search_s.
             # Note that some DNs have various extra whitespace added and mixed
             # up case since LDAP is pretty loose about these.
-            self.records = (
+            self.records = records
+
+
+        def search_s(self, base, scope, filterstr="(objectClass=*)",
+            attrlist=None):
+            """ A simple implementation of LDAP search filter processing """
+
+            base = normalizeDNstr(base)
+            results = []
+            for dn, attrs in self.records:
+                dn = normalizeDNstr(dn)
+                if dn == base:
+                    results.append(("ignored", (dn, attrs)))
+                elif dnContainedIn(ldap.dn.str2dn(dn), ldap.dn.str2dn(base)):
+                    if filterstr in ("(objectClass=*)", "(!(objectClass=organizationalUnit))"):
+                        results.append(("ignored", (dn, attrs)))
+                    else:
+                        trans = maketrans("&(|)", "   |")
+                        fragments = filterstr.encode("utf-8").translate(trans).split("|")
+                        for fragment in fragments:
+                            if not fragment:
+                                continue
+                            fragment = fragment.strip()
+                            key, value = fragment.split("=")
+                            if value in attrs.get(key, []):
+                                results.append(("ignored", (dn, attrs)))
+
+            return results
+
+
+    class LdapDirectoryServiceTestCase(TestCase):
+
+        nestedUsingDifferentAttributeUsingDN = (
+            (
                 (
                     "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
                     {
                         'cn': ['recursive1_coasts'],
                         'apple-generateduid': ['recursive1_coasts'],
                         'uniqueMember': [
-                            'cn=recursive2_coasts,cn=groups,dc=example,dc=com',
                             'uid=wsanchez ,cn=users, dc=eXAMple,dc=com',
                         ],
+                        'nestedGroups': [
+                            'cn=recursive2_coasts,cn=groups,dc=example,dc=com',
+                        ],
                     }
                 ),
                 (
@@ -321,12 +356,229 @@
                         'cn': ['recursive2_coasts'],
                         'apple-generateduid': ['recursive2_coasts'],
                         'uniqueMember': [
+                            'uid=cdaboo,cn=users,dc=example,dc=com',
+                        ],
+                        'nestedGroups': [
                             'cn=recursive1_coasts,cn=groups,dc=example,dc=com',
+                        ],
+                    }
+                ),
+                (
+                    'cn=both_coasts,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['both_coasts'],
+                        'apple-generateduid': ['both_coasts'],
+                        'nestedGroups': [
+                            'cn=right_coast,cn=groups,dc=example,dc=com',
+                            'cn=left_coast,cn=groups,dc=example,dc=com',
+                        ],
+                    }
+                ),
+                (
+                    'cn=right_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['right_coast'],
+                        'apple-generateduid': ['right_coast'],
+                        'uniqueMember': [
                             'uid=cdaboo,cn=users,dc=example,dc=com',
                         ],
                     }
                 ),
                 (
+                    'cn=left_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['left_coast'],
+                        'apple-generateduid': ['left_coast'],
+                        'uniqueMember': [
+                            'uid=wsanchez, cn=users,dc=example,dc=com',
+                            'uid=lecroy,cn=users,dc=example,dc=com',
+                            'uid=dreid,cn=users,dc=example,dc=com',
+                        ],
+                    }
+                ),
+                (
+                    "uid=odtestamanda,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestamanda'],
+                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
+                        'sn': ['Test'],
+                        'mail': ['odtestamanda at example.com', 'alternate at example.com'],
+                        'givenName': ['Amanda'],
+                        'cn': ['Amanda Test']
+                    }
+                ),
+                (
+                    "uid=odtestbetty,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestbetty'],
+                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
+                        'sn': ['Test'],
+                        'mail': ['odtestbetty at example.com'],
+                        'givenName': ['Betty'],
+                        'cn': ['Betty Test']
+                    }
+                ),
+                (
+                    "uid=odtestcarlene,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestcarlene'],
+                        # Note: no guid here, to test this record is skipped
+                        'sn': ['Test'],
+                        'mail': ['odtestcarlene at example.com'],
+                        'givenName': ['Carlene'],
+                        'cn': ['Carlene Test']
+                    }
+                ),
+                (
+                    "uid=cdaboo,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['cdaboo'],
+                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                        'sn': ['Daboo'],
+                        'mail': ['daboo at example.com'],
+                        'givenName': ['Cyrus'],
+                        'cn': ['Cyrus Daboo']
+                    }
+                ),
+                (
+                    "uid=wsanchez  ,  cn=users  , dc=example,dc=com",
+                    {
+                        'uid': ['wsanchez'],
+                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
+                        'sn': ['Sanchez'],
+                        'mail': ['wsanchez at example.com'],
+                        'givenName': ['Wilfredo'],
+                        'cn': ['Wilfredo Sanchez']
+                    }
+                ),
+            ),
+            {
+                "augmentService" : None,
+                "groupMembershipCache" : None,
+                "cacheTimeout": 1, # Minutes
+                "negativeCaching": False,
+                "warningThresholdSeconds": 3,
+                "batchSize": 500,
+                "queryLocationsImplicitly": True,
+                "restrictEnabledRecords": True,
+                "restrictToGroup": "both_coasts",
+                "recordTypes": ("users", "groups", "locations", "resources"),
+                "uri": "ldap://localhost/",
+                "tls": False,
+                "tlsCACertFile": None,
+                "tlsCACertDir": None,
+                "tlsRequireCert": None, # never, allow, try, demand, hard
+                "credentials": {
+                    "dn": None,
+                    "password": None,
+                },
+                "authMethod": "LDAP",
+                "rdnSchema": {
+                    "base": "dc=example,dc=com",
+                    "guidAttr": "apple-generateduid",
+                    "users": {
+                        "rdn": "cn=Users",
+                        "attr": "uid", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "loginEnabledAttr" : "", # attribute controlling login
+                        "loginEnabledValue" : "yes", # "True" value of above attribute
+                        "calendarEnabledAttr" : "enable-calendar", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "uid",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "groups": {
+                        "rdn": "cn=Groups",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "locations": {
+                        "rdn": "cn=Places",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : "", # old style, single string
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "resources": {
+                        "rdn": "cn=Resources",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : [], # new style, array
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                },
+                "groupSchema": {
+                    "membersAttr": "uniqueMember", # how members are specified
+                    "nestedGroupsAttr": "nestedGroups", # how nested groups are specified
+                    "memberIdAttr": "", # which attribute the above refer to
+                },
+                "resourceSchema": {
+                    "resourceInfoAttr": "apple-resource-info", # contains location/resource info
+                    "autoScheduleAttr": None,
+                    "proxyAttr": None,
+                    "readOnlyProxyAttr": None,
+                },
+                "partitionSchema": {
+                    "serverIdAttr": "server-id", # maps to augments server-id
+                    "partitionIdAttr": "partition-id", # maps to augments partition-id
+                },
+            }
+        )
+        nestedUsingSameAttributeUsingDN = (
+            (
+                (
+                    "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
+                    {
+                        'cn': ['recursive1_coasts'],
+                        'apple-generateduid': ['recursive1_coasts'],
+                        'uniqueMember': [
+                            'uid=wsanchez ,cn=users, dc=eXAMple,dc=com',
+                            'cn=recursive2_coasts,cn=groups,dc=example,dc=com',
+                        ],
+                    }
+                ),
+                (
+                    "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
+                    {
+                        'cn': ['recursive2_coasts'],
+                        'apple-generateduid': ['recursive2_coasts'],
+                        'uniqueMember': [
+                            'uid=cdaboo,cn=users,dc=example,dc=com',
+                            'cn=recursive1_coasts,cn=groups,dc=example,dc=com',
+                        ],
+                    }
+                ),
+                (
                     'cn=both_coasts,cn=groups,dc=example,dc=com',
                     {
                         'cn': ['both_coasts'],
@@ -414,41 +666,8 @@
                         'cn': ['Wilfredo Sanchez']
                     }
                 ),
-            )
-
-        def search_s(self, base, scope, filterstr="(objectClass=*)",
-            attrlist=None):
-            """ A simple implementation of LDAP search filter processing """
-
-            base = normalizeDNstr(base)
-            results = []
-            for dn, attrs in self.records:
-                dn = normalizeDNstr(dn)
-                if dn == base:
-                    results.append(("ignored", (dn, attrs)))
-                elif dnContainedIn(ldap.dn.str2dn(dn), ldap.dn.str2dn(base)):
-                    if filterstr in ("(objectClass=*)", "(!(objectClass=organizationalUnit))"):
-                        results.append(("ignored", (dn, attrs)))
-                    else:
-                        trans = maketrans("&(|)", "   |")
-                        fragments = filterstr.encode("utf-8").translate(trans).split("|")
-                        for fragment in fragments:
-                            if not fragment:
-                                continue
-                            fragment = fragment.strip()
-                            key, value = fragment.split("=")
-                            if value in attrs.get(key, []):
-                                results.append(("ignored", (dn, attrs)))
-
-            return results
-
-
-    class LdapDirectoryServiceTestCase(TestCase):
-
-        def setUp(self):
-            super(LdapDirectoryServiceTestCase, self).setUp()
-
-            params = {
+            ),
+            {
                 "augmentService" : None,
                 "groupMembershipCache" : None,
                 "cacheTimeout": 1, # Minutes
@@ -456,8 +675,8 @@
                 "warningThresholdSeconds": 3,
                 "batchSize": 500,
                 "queryLocationsImplicitly": True,
-                "restrictEnabledRecords": False,
-                "restrictToGroup": "",
+                "restrictEnabledRecords": True,
+                "restrictToGroup": "both_coasts",
                 "recordTypes": ("users", "groups", "locations", "resources"),
                 "uri": "ldap://localhost/",
                 "tls": False,
@@ -549,9 +768,441 @@
                     "partitionIdAttr": "partition-id", # maps to augments partition-id
                 },
             }
+        )
+        nestedUsingDifferentAttributeUsingGUID = (
+            (
+                (
+                    "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
+                    {
+                        'cn': ['recursive1_coasts'],
+                        'apple-generateduid': ['recursive1_coasts'],
+                        'uniqueMember': [
+                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
+                        ],
+                        'nestedGroups': [
+                            'recursive2_coasts',
+                        ],
+                    }
+                ),
+                (
+                    "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
+                    {
+                        'cn': ['recursive2_coasts'],
+                        'apple-generateduid': ['recursive2_coasts'],
+                        'uniqueMember': [
+                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
+                        ],
+                        'nestedGroups': [
+                            'recursive1_coasts',
+                        ],
+                    }
+                ),
+                (
+                    'cn=both_coasts,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['both_coasts'],
+                        'apple-generateduid': ['both_coasts'],
+                        'nestedGroups': [
+                            'right_coast',
+                            'left_coast',
+                        ],
+                    }
+                ),
+                (
+                    'cn=right_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['right_coast'],
+                        'apple-generateduid': ['right_coast'],
+                        'uniqueMember': [
+                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
+                        ],
+                    }
+                ),
+                (
+                    'cn=left_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['left_coast'],
+                        'apple-generateduid': ['left_coast'],
+                        'uniqueMember': [
+                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
+                        ],
+                    }
+                ),
+                (
+                    "uid=odtestamanda,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestamanda'],
+                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
+                        'sn': ['Test'],
+                        'mail': ['odtestamanda at example.com', 'alternate at example.com'],
+                        'givenName': ['Amanda'],
+                        'cn': ['Amanda Test']
+                    }
+                ),
+                (
+                    "uid=odtestbetty,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestbetty'],
+                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
+                        'sn': ['Test'],
+                        'mail': ['odtestbetty at example.com'],
+                        'givenName': ['Betty'],
+                        'cn': ['Betty Test']
+                    }
+                ),
+                (
+                    "uid=odtestcarlene,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestcarlene'],
+                        # Note: no guid here, to test this record is skipped
+                        'sn': ['Test'],
+                        'mail': ['odtestcarlene at example.com'],
+                        'givenName': ['Carlene'],
+                        'cn': ['Carlene Test']
+                    }
+                ),
+                (
+                    "uid=cdaboo,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['cdaboo'],
+                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                        'sn': ['Daboo'],
+                        'mail': ['daboo at example.com'],
+                        'givenName': ['Cyrus'],
+                        'cn': ['Cyrus Daboo']
+                    }
+                ),
+                (
+                    "uid=wsanchez  ,  cn=users  , dc=example,dc=com",
+                    {
+                        'uid': ['wsanchez'],
+                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
+                        'sn': ['Sanchez'],
+                        'mail': ['wsanchez at example.com'],
+                        'givenName': ['Wilfredo'],
+                        'cn': ['Wilfredo Sanchez']
+                    }
+                ),
+            ),
+            {
+                "augmentService" : None,
+                "groupMembershipCache" : None,
+                "cacheTimeout": 1, # Minutes
+                "negativeCaching": False,
+                "warningThresholdSeconds": 3,
+                "batchSize": 500,
+                "queryLocationsImplicitly": True,
+                "restrictEnabledRecords": True,
+                "restrictToGroup": "both_coasts",
+                "recordTypes": ("users", "groups", "locations", "resources"),
+                "uri": "ldap://localhost/",
+                "tls": False,
+                "tlsCACertFile": None,
+                "tlsCACertDir": None,
+                "tlsRequireCert": None, # never, allow, try, demand, hard
+                "credentials": {
+                    "dn": None,
+                    "password": None,
+                },
+                "authMethod": "LDAP",
+                "rdnSchema": {
+                    "base": "dc=example,dc=com",
+                    "guidAttr": "apple-generateduid",
+                    "users": {
+                        "rdn": "cn=Users",
+                        "attr": "uid", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "loginEnabledAttr" : "", # attribute controlling login
+                        "loginEnabledValue" : "yes", # "True" value of above attribute
+                        "calendarEnabledAttr" : "enable-calendar", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "uid",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "groups": {
+                        "rdn": "cn=Groups",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "locations": {
+                        "rdn": "cn=Places",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : "", # old style, single string
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "resources": {
+                        "rdn": "cn=Resources",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : [], # new style, array
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                },
+                "groupSchema": {
+                    "membersAttr": "uniqueMember", # how members are specified
+                    "nestedGroupsAttr": "nestedGroups", # how nested groups are specified
+                    "memberIdAttr": "apple-generateduid", # which attribute the above refer to
+                },
+                "resourceSchema": {
+                    "resourceInfoAttr": "apple-resource-info", # contains location/resource info
+                    "autoScheduleAttr": None,
+                    "proxyAttr": None,
+                    "readOnlyProxyAttr": None,
+                },
+                "partitionSchema": {
+                    "serverIdAttr": "server-id", # maps to augments server-id
+                    "partitionIdAttr": "partition-id", # maps to augments partition-id
+                },
+            }
+        )
+        nestedUsingSameAttributeUsingGUID = (
+            (
+                (
+                    "cn=Recursive1_coasts, cn=gROUps,dc=example, dc=com",
+                    {
+                        'cn': ['recursive1_coasts'],
+                        'apple-generateduid': ['recursive1_coasts'],
+                        'uniqueMember': [
+                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
+                            'recursive2_coasts',
+                        ],
+                    }
+                ),
+                (
+                    "cn=recursive2_coasts,cn=groups,dc=example,dc=com",
+                    {
+                        'cn': ['recursive2_coasts'],
+                        'apple-generateduid': ['recursive2_coasts'],
+                        'uniqueMember': [
+                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
+                            'recursive1_coasts',
+                        ],
+                    }
+                ),
+                (
+                    'cn=both_coasts,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['both_coasts'],
+                        'apple-generateduid': ['both_coasts'],
+                        'uniqueMember': [
+                            'right_coast',
+                            'left_coast',
+                        ],
+                    }
+                ),
+                (
+                    'cn=right_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['right_coast'],
+                        'apple-generateduid': ['right_coast'],
+                        'uniqueMember': [
+                            '5A985493-EE2C-4665-94CF-4DFEA3A89500',
+                        ],
+                    }
+                ),
+                (
+                    'cn=left_coast,cn=groups,dc=example,dc=com',
+                    {
+                        'cn': ['left_coast'],
+                        'apple-generateduid': ['left_coast'],
+                        'uniqueMember': [
+                            '6423F94A-6B76-4A3A-815B-D52CFD77935D',
+                        ],
+                    }
+                ),
+                (
+                    "uid=odtestamanda,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestamanda'],
+                        'apple-generateduid': ['9DC04A70-E6DD-11DF-9492-0800200C9A66'],
+                        'sn': ['Test'],
+                        'mail': ['odtestamanda at example.com', 'alternate at example.com'],
+                        'givenName': ['Amanda'],
+                        'cn': ['Amanda Test']
+                    }
+                ),
+                (
+                    "uid=odtestbetty,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestbetty'],
+                        'apple-generateduid': ['93A8F5C5-49D8-4641-840F-CD1903B0394C'],
+                        'sn': ['Test'],
+                        'mail': ['odtestbetty at example.com'],
+                        'givenName': ['Betty'],
+                        'cn': ['Betty Test']
+                    }
+                ),
+                (
+                    "uid=odtestcarlene,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['odtestcarlene'],
+                        # Note: no guid here, to test this record is skipped
+                        'sn': ['Test'],
+                        'mail': ['odtestcarlene at example.com'],
+                        'givenName': ['Carlene'],
+                        'cn': ['Carlene Test']
+                    }
+                ),
+                (
+                    "uid=cdaboo,cn=users,dc=example,dc=com",
+                    {
+                        'uid': ['cdaboo'],
+                        'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                        'sn': ['Daboo'],
+                        'mail': ['daboo at example.com'],
+                        'givenName': ['Cyrus'],
+                        'cn': ['Cyrus Daboo']
+                    }
+                ),
+                (
+                    "uid=wsanchez  ,  cn=users  , dc=example,dc=com",
+                    {
+                        'uid': ['wsanchez'],
+                        'apple-generateduid': ['6423F94A-6B76-4A3A-815B-D52CFD77935D'],
+                        'sn': ['Sanchez'],
+                        'mail': ['wsanchez at example.com'],
+                        'givenName': ['Wilfredo'],
+                        'cn': ['Wilfredo Sanchez']
+                    }
+                ),
+            ),
+            {
+                "augmentService" : None,
+                "groupMembershipCache" : None,
+                "cacheTimeout": 1, # Minutes
+                "negativeCaching": False,
+                "warningThresholdSeconds": 3,
+                "batchSize": 500,
+                "queryLocationsImplicitly": True,
+                "restrictEnabledRecords": True,
+                "restrictToGroup": "both_coasts",
+                "recordTypes": ("users", "groups", "locations", "resources"),
+                "uri": "ldap://localhost/",
+                "tls": False,
+                "tlsCACertFile": None,
+                "tlsCACertDir": None,
+                "tlsRequireCert": None, # never, allow, try, demand, hard
+                "credentials": {
+                    "dn": None,
+                    "password": None,
+                },
+                "authMethod": "LDAP",
+                "rdnSchema": {
+                    "base": "dc=example,dc=com",
+                    "guidAttr": "apple-generateduid",
+                    "users": {
+                        "rdn": "cn=Users",
+                        "attr": "uid", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "loginEnabledAttr" : "", # attribute controlling login
+                        "loginEnabledValue" : "yes", # "True" value of above attribute
+                        "calendarEnabledAttr" : "enable-calendar", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "uid",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "groups": {
+                        "rdn": "cn=Groups",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "", # additional filter for this type
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : ["mail", "emailAliases"],
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "locations": {
+                        "rdn": "cn=Places",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : "", # old style, single string
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                    "resources": {
+                        "rdn": "cn=Resources",
+                        "attr": "cn", # used only to synthesize email address
+                        "emailSuffix": None, # used only to synthesize email address
+                        "filter": "(objectClass=apple-resource)", # additional filter for this type
+                        "calendarEnabledAttr" : "", # attribute controlling calendaring
+                        "calendarEnabledValue" : "yes", # "True" value of above attribute
+                        "mapping": { # maps internal record names to LDAP
+                            "recordName": "cn",
+                            "fullName" : "cn",
+                            "emailAddresses" : [], # new style, array
+                            "firstName" : "givenName",
+                            "lastName" : "sn",
+                        },
+                    },
+                },
+                "groupSchema": {
+                    "membersAttr": "uniqueMember", # how members are specified
+                    "nestedGroupsAttr": "", # how nested groups are specified
+                    "memberIdAttr": "apple-generateduid", # which attribute the above refer to
+                },
+                "resourceSchema": {
+                    "resourceInfoAttr": "apple-resource-info", # contains location/resource info
+                    "autoScheduleAttr": None,
+                    "proxyAttr": None,
+                    "readOnlyProxyAttr": None,
+                },
+                "partitionSchema": {
+                    "serverIdAttr": "server-id", # maps to augments server-id
+                    "partitionIdAttr": "partition-id", # maps to augments partition-id
+                },
+            }
+        )
 
-            self.service = LdapDirectoryService(params)
-            self.service.ldap = LdapDirectoryTestWrapper(self.service.ldap)
+        def setupService(self, scenario):
+            self.service = LdapDirectoryService(scenario[1])
+            self.service.ldap = LdapDirectoryTestWrapper(self.service.ldap, scenario[0])
             self.patch(ldap, "async", StubAsync())
 
 
@@ -559,6 +1210,8 @@
             """
             Exercise the fake search_s implementation
             """
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
+
             # Get all groups
             self.assertEquals(
                 len(self.service.ldap.search_s("cn=groups,dc=example,dc=com", 0, "(objectClass=*)", [])), 5)
@@ -574,6 +1227,7 @@
             Exercise _ldapResultToRecord(), which converts a dictionary
             of LDAP attributes into an LdapDirectoryRecord
             """
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
 
             # User without enabled-for-calendaring specified
 
@@ -805,6 +1459,7 @@
             listRecords makes an LDAP query (with fake results in this test)
             and turns the results into records
             """
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
 
             records = self.service.listRecords(self.service.recordType_users)
             self.assertEquals(len(records), 4)
@@ -813,6 +1468,97 @@
                 set(["Amanda", "Betty", "Cyrus", "Wilfredo"]) # Carlene is skipped because no guid in LDAP
             )
 
+        def test_restrictedPrincipalsUsingDN(self):
+            """
+            If restrictToGroup is in place, restrictedPrincipals should return only the principals
+            within that group.  In this case we're testing scenarios in which membership
+            is specified by DN
+            """
+            for scenario in (
+                self.nestedUsingSameAttributeUsingDN,
+                self.nestedUsingDifferentAttributeUsingDN,
+                ):
+                self.setupService(scenario)
+
+                self.assertEquals(
+                    set([
+                        "cn=left_coast,cn=groups,dc=example,dc=com",
+                        "cn=right_coast,cn=groups,dc=example,dc=com",
+                        "uid=cdaboo,cn=users,dc=example,dc=com",
+                        "uid=dreid,cn=users,dc=example,dc=com",
+                        "uid=lecroy,cn=users,dc=example,dc=com",
+                        "uid=wsanchez,cn=users,dc=example,dc=com",
+                    ]),
+                    self.service.restrictedPrincipals)
+
+                dn = "uid=cdaboo,cn=users,dc=example,dc=com"
+                attrs = {
+                    'uid': ['cdaboo'],
+                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                    'sn': ['Daboo'],
+                    'mail': ['daboo at example.com'],
+                    'givenName': ['Cyrus'],
+                    'cn': ['Cyrus Daboo']
+                }
+                self.assertTrue(self.service.isAllowedByRestrictToGroup(dn, attrs))
+
+                dn = "uid=unknown,cn=users,dc=example,dc=com"
+                attrs = {
+                    'uid': ['unknown'],
+                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                    'sn': ['unknown'],
+                    'mail': ['unknown at example.com'],
+                    'givenName': ['unknown'],
+                    'cn': ['unknown']
+                }
+                self.assertFalse(self.service.isAllowedByRestrictToGroup(dn, attrs))
+
+
+        def test_restrictedPrincipalsUsingGUID(self):
+            """
+            If restrictToGroup is in place, restrictedPrincipals should return only the principals
+            within that group.  In this case we're testing scenarios in which membership
+            is specified by an attribute, not DN
+            """
+            for scenario in (
+                self.nestedUsingDifferentAttributeUsingGUID,
+                self.nestedUsingSameAttributeUsingGUID,
+                ):
+                self.setupService(scenario)
+
+                self.assertEquals(
+                    set([
+                        "left_coast",
+                        "right_coast",
+                        "5A985493-EE2C-4665-94CF-4DFEA3A89500",
+                        "6423F94A-6B76-4A3A-815B-D52CFD77935D",
+                    ]),
+                    self.service.restrictedPrincipals)
+
+                dn = "uid=cdaboo,cn=users,dc=example,dc=com"
+                attrs = {
+                    'uid': ['cdaboo'],
+                    'apple-generateduid': ['5A985493-EE2C-4665-94CF-4DFEA3A89500'],
+                    'sn': ['Daboo'],
+                    'mail': ['daboo at example.com'],
+                    'givenName': ['Cyrus'],
+                    'cn': ['Cyrus Daboo']
+                }
+                self.assertTrue(self.service.isAllowedByRestrictToGroup(dn, attrs))
+
+                dn = "uid=unknown,cn=users,dc=example,dc=com"
+                attrs = {
+                    'uid': ['unknown'],
+                    'apple-generateduid': ['unknown'],
+                    'sn': ['unknown'],
+                    'mail': ['unknown at example.com'],
+                    'givenName': ['unknown'],
+                    'cn': ['unknown']
+                }
+                self.assertFalse(self.service.isAllowedByRestrictToGroup(dn, attrs))
+
+
+
         @inlineCallbacks
         def test_groupMembershipAliases(self):
             """
@@ -823,6 +1569,7 @@
             guids, updateCache( ) is smart enough to map between guids and this
             attribute which is referred to in the code as record.cachedGroupsAlias().
             """
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
 
             # Set up proxydb and preload it from xml
             calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB("proxies.sqlite")
@@ -848,6 +1595,7 @@
 
 
         def test_splitIntoBatches(self):
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
             # Data is perfect multiple of size
             results = list(splitIntoBatches(set(range(12)), 4))
             self.assertEquals(results,
@@ -865,6 +1613,7 @@
         def test_recordTypeForDN(self):
             # Ensure dn comparison is case insensitive and ignores extra
             # whitespace
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
 
             # Base DNs for each recordtype should already be lowercase
             for dn in self.service.typeDNs.itervalues():
@@ -903,6 +1652,7 @@
             Verify queryDirectory skips LDAP queries where there has been no
             LDAP attribute mapping provided for the given index type.
             """
+            self.setupService(self.nestedUsingDifferentAttributeUsingDN)
 
             self.history = []
 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120823/56e5e789/attachment-0001.html>


More information about the calendarserver-changes mailing list