[CalendarServer-changes] [7855] CalendarServer/trunk/twistedcaldav/directory
source_changes at macosforge.org
source_changes at macosforge.org
Tue Aug 2 19:58:18 PDT 2011
Revision: 7855
http://trac.macosforge.org/projects/calendarserver/changeset/7855
Author: sagen at apple.com
Date: 2011-08-02 19:58:18 -0700 (Tue, 02 Aug 2011)
Log Message:
-----------
Deal with nested groups when members are referred to by an attribute other than guid; use pickle mode in GroupMembershipCache so that commas in DNs don't foul up lists of members
Modified Paths:
--------------
CalendarServer/trunk/twistedcaldav/directory/directory.py
CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py 2011-08-02 18:04:28 UTC (rev 7854)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py 2011-08-03 02:58:18 UTC (rev 7855)
@@ -406,7 +406,7 @@
"""
- def __init__(self, namespace, pickle=False, no_invalidation=False,
+ def __init__(self, namespace, pickle=True, no_invalidation=False,
key_normalization=True, expireSeconds=0):
super(GroupMembershipCache, self).__init__(namespace, pickle=pickle,
@@ -418,14 +418,14 @@
def setGroupsFor(self, guid, memberships):
self.log_debug("set groups-for %s : %s" % (guid, memberships))
return self.set("groups-for:%s" %
- (str(guid)), str(",".join(memberships)),
+ (str(guid)), memberships,
expireTime=self.expireSeconds)
def getGroupsFor(self, guid):
self.log_debug("get groups-for %s" % (guid,))
def _value(value):
if value:
- return set(value.split(","))
+ return value
else:
return set()
d = self.get("groups-for:%s" % (str(guid),))
@@ -476,15 +476,23 @@
def getGroups(self):
"""
Retrieve all groups and their member info (but don't actually fault in
- the records of the members), and return a dictionary of group-guid to
- member-guids. Ultimately this dictionary will be used to reverse-index
- the groups that users are in by expandedMembers().
+ the records of the members), and return two dictionaries. The first maps
+ group-guid to members. The keys will be guids, the values are lists of
+ members usually specified by guid, but in a directory system like LDAP which
+ can use a different attribute to refer to members this could be a DN. The
+ second dictionary returns maps that member attribute back to the corresponding
+ guid. These dictionaries are used to reverse-index the groups that users are
+ in by expandedMembers().
"""
groups = {}
+ aliases = {}
for record in self.directory.listRecords(self.directory.recordType_groups):
- groups[record.guid] = record.memberGUIDs()
- return groups
+ alias = record.cachedGroupsAlias()
+ groups[alias] = record.memberGUIDs()
+ aliases[record.guid] = alias
+ return groups, aliases
+
def expandedMembers(self, groups, guid, members=None, seen=None):
"""
Return the complete, flattened set of members of a group, including
@@ -503,9 +511,9 @@
if groups.has_key(member): # it's a group then
self.expandedMembers(groups, member, members=members,
seen=seen)
-
return members
+
@inlineCallbacks
def updateCache(self, fast=False):
"""
@@ -525,6 +533,12 @@
the (possibly modified) value for fast, and the number of members loaded
into the cache (which can be zero if fast=True and isPopulated(), or
fast=False and the cache is locked by someone else).
+
+ The pickled snapshot file is a dict whose keys represent a record and
+ the values are the guids of the groups that record is a member of. The
+ keys are normally guids except in the case of a directory system like LDAP
+ where there can be a different attribute used for referring to members,
+ such as a DN.
"""
# TODO: add memcached eviction protection
@@ -594,12 +608,18 @@
# containing which delegated-to groups a user is a member of
self.log_info("Retrieving list of all proxies")
+ # This is always a set of guids:
delegatedGUIDs = set((yield self.proxyDB.getAllMembers()))
self.log_info("There are %d proxies" % (len(delegatedGUIDs),))
+ self.log_info("Retrieving group hierarchy from directory")
- self.log_info("Retrieving group hierarchy from directory")
- groups = self.getGroups()
- groupGUIDs = set(groups.keys())
+ # "groups" maps a group to its members; the keys and values consist
+ # of whatever directory attribute is used to refer to members. The
+ # attribute value comes from record.cachedGroupsAlias().
+ # "aliases" maps the record.cachedGroupsAlias() value for a group
+ # back to the group's guid.
+ groups, aliases = self.getGroups()
+ groupGUIDs = set(aliases.keys())
self.log_info("There are %d groups in the directory" %
(len(groupGUIDs),))
@@ -609,7 +629,8 @@
# Reverse index the group membership from cache
members = {}
for groupGUID in delegatedGUIDs:
- groupMembers = self.expandedMembers(groups, groupGUID)
+ groupMembers = self.expandedMembers(groups, aliases[groupGUID])
+ # groupMembers is in cachedGroupsAlias() format
for member in groupMembers:
memberships = members.setdefault(member, set())
memberships.add(groupGUID)
@@ -1034,8 +1055,19 @@
Return the set of groups (guids) this record is a member of, based on
the data cached by cacheGroupMembership( )
"""
- return self.service.groupMembershipCache.getGroupsFor(self.guid)
+ return self.service.groupMembershipCache.getGroupsFor(self.cachedGroupsAlias())
+ def cachedGroupsAlias(self):
+ """
+ The GroupMembershipCache uses keys based on this value. Normally it's
+ a record's guid but in a directory system like LDAP which can use a
+ different attribute to refer to group members, we need to be able to
+ look up an entry in the GroupMembershipCache by that attribute.
+ Subclasses which don't use record.guid to look up group membership
+ should override this method.
+ """
+ return self.guid
+
def externalProxies(self):
"""
Return the set of proxies defined in the directory service, as opposed
Modified: CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py 2011-08-02 18:04:28 UTC (rev 7854)
+++ CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py 2011-08-03 02:58:18 UTC (rev 7855)
@@ -1035,12 +1035,14 @@
return groups
- def cachedGroups(self):
+ def cachedGroupsAlias(self):
"""
- Return the set of groups (guids) this record is a member of, based on
- the data cached by cacheGroupMembership( )
+ See directory.py for full description
+
+ LDAP group members can be referred to by attributes other than guid. _memberId
+ will be set to the appropriate value to look up group-membership with.
"""
- return self.service.groupMembershipCache.getGroupsFor(self._memberId)
+ return self._memberId
def memberGUIDs(self):
return set(self._memberGUIDs)
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py 2011-08-02 18:04:28 UTC (rev 7854)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py 2011-08-03 02:58:18 UTC (rev 7855)
@@ -162,11 +162,11 @@
"""
cache = GroupMembershipCache("ProxyDB", expireSeconds=10)
- yield cache.setGroupsFor("a", ["b", "c", "d"]) # a is in b, c, d
+ yield cache.setGroupsFor("a", set(["b", "c", "d"])) # a is in b, c, d
members = (yield cache.getGroupsFor("a"))
self.assertEquals(members, set(["b", "c", "d"]))
- yield cache.setGroupsFor("b", []) # b not in any groups
+ yield cache.setGroupsFor("b", set()) # b not in any groups
members = (yield cache.getGroupsFor("b"))
self.assertEquals(members, set())
@@ -194,7 +194,7 @@
cache=cache, useExternalProxies=False)
# Exercise getGroups()
- groups = updater.getGroups()
+ groups, aliases = updater.getGroups()
self.assertEquals(
groups,
{
@@ -225,6 +225,21 @@
set(['5A985493-EE2C-4665-94CF-4DFEA3A89500'])
}
)
+ self.assertEquals(
+ aliases,
+ {
+ '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1':
+ '9FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1',
+ 'admin': 'admin',
+ 'both_coasts': 'both_coasts',
+ 'grunts': 'grunts',
+ 'left_coast': 'left_coast',
+ 'non_calendar_group': 'non_calendar_group',
+ 'recursive1_coasts': 'recursive1_coasts',
+ 'recursive2_coasts': 'recursive2_coasts',
+ 'right_coast': 'right_coast'
+ }
+ )
# Exercise expandedMembers()
self.assertEquals(
Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py 2011-08-02 18:04:28 UTC (rev 7854)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py 2011-08-03 02:58:18 UTC (rev 7855)
@@ -18,6 +18,11 @@
from twistedcaldav.directory.ldapdirectory import (
buildFilter, LdapDirectoryService, MissingGuidException
)
+ from twistedcaldav.test.util import proxiesFile
+ from twistedcaldav.directory.calendaruserproxyloader import XMLCalendarUserProxyLoader
+ from twistedcaldav.directory import calendaruserproxy
+ from twistedcaldav.directory.directory import GroupMembershipCache, GroupMembershipCacheUpdater
+ from twisted.internet.defer import inlineCallbacks
except ImportError:
print "Skipping because ldap module not installed"
else:
@@ -108,6 +113,8 @@
class LdapDirectoryServiceTestCase(TestCase):
def setUp(self):
+ super(LdapDirectoryServiceTestCase, self).setUp()
+
params = {
"augmentService" : None,
"groupMembershipCache" : None,
@@ -191,9 +198,9 @@
},
},
"groupSchema": {
- "membersAttr": "apple-group-memberguid", # how members are specified
- "nestedGroupsAttr": "apple-group-nestedgroup", # how nested groups are specified
- "memberIdAttr": "apple-generateduid", # which attribute the above refer to
+ "membersAttr": "uniqueMember", # how members are specified
+ "nestedGroupsAttr": "", # how nested groups are specified
+ "memberIdAttr": "", # which attribute the above refer to
},
"resourceSchema": {
"resourceInfoAttr": "apple-resource-info", # contains location/resource info
@@ -296,13 +303,10 @@
guid = '6C6CD280-E6E3-11DF-9492-0800200C9A66'
attrs = {
'apple-generateduid': [guid],
- 'apple-group-memberguid':
+ 'uniqueMember':
[
'9DC04A70-E6DD-11DF-9492-0800200C9A66',
- '9DC04A71-E6DD-11DF-9492-0800200C9A66'
- ],
- 'apple-group-nestedgroup':
- [
+ '9DC04A71-E6DD-11DF-9492-0800200C9A66',
'6C6CD282-E6E3-11DF-9492-0800200C9A66'
],
'cn': ['odtestgrouptop']
@@ -454,3 +458,85 @@
set([r.firstName for r in records]),
set(["Amanda", "Betty"]) # Carlene is skipped because no guid in LDAP
)
+
+ @inlineCallbacks
+ def test_groupMembershipAliases(self):
+ """
+ Exercise a directory enviornment where group membership does not refer
+ to guids but instead uses LDAP DNs. This example uses the LDAP attribute
+ "uniqueMember" to specify members of a group. The value of this attribute
+ is each members' DN. Even though the proxy database deals strictly in
+ guids, updateCache( ) is smart enough to map between guids and this
+ attribute which is referred to in the code as record.cachedGroupsAlias().
+ """
+
+ # Set up proxydb and preload it from xml
+ calendaruserproxy.ProxyDBService = calendaruserproxy.ProxySqliteDB("proxies.sqlite")
+ yield XMLCalendarUserProxyLoader(proxiesFile.path).updateProxyDB()
+
+ # Set up the GroupMembershipCache
+ cache = GroupMembershipCache("ProxyDB", expireSeconds=60)
+ self.service.groupMembershipCache = cache
+ updater = GroupMembershipCacheUpdater(calendaruserproxy.ProxyDBService,
+ self.service, 30, cache=cache, useExternalProxies=False)
+
+ # Fake LDAP results for the group listRecords performed within updateCache()
+ self.service.ldap.setTestResults([
+ (
+ "cn=bothcoasts,cn=groups,dc=example,dc=com",
+ {
+ 'cn': ['topgroup'],
+ 'apple-generateduid': ['both_coasts'],
+ 'uniqueMember': [
+ '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',
+ ],
+ }
+ ),
+ ])
+
+ self.assertEquals((False, 6), (yield updater.updateCache()))
+
+ users = self.service.recordType_users
+
+ for shortName, groups in [
+ ("cdaboo", set(["both_coasts"])),
+ ("wsanchez", set(["both_coasts", "left_coast"])),
+ ]:
+
+ # Fake LDAP results for the record lookup
+ self.service.ldap.setTestResults([
+ (
+ "uid=%s,cn=users,dc=example,dc=com" % (shortName,),
+ {
+ 'uid': [shortName],
+ 'cn': [shortName],
+ 'apple-generateduid': [shortName],
+ }
+ ),
+ ])
+
+ record = self.service.recordWithShortName(users, shortName)
+ self.assertEquals(groups, (yield record.cachedGroups()))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20110802/2ae4c036/attachment-0001.html>
More information about the calendarserver-changes
mailing list