[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