Revision: 3573 http://trac.macosforge.org/projects/calendarserver/changeset/3573 Author: cdaboo@apple.com Date: 2009-01-07 10:15:56 -0800 (Wed, 07 Jan 2009) Log Message: ----------- Delete entries from the delegate DB if users are no longer in the directory. Modified Paths: -------------- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py CalendarServer/trunk/twistedcaldav/directory/principal.py CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py Modified: CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py =================================================================== --- CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2009-01-06 20:23:10 UTC (rev 3572) +++ CalendarServer/trunk/twistedcaldav/directory/calendaruserproxy.py 2009-01-07 18:15:56 UTC (rev 3573) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2008 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -70,7 +70,7 @@ return davxml.ACL(*aces) def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None): - # Permissions here are fixed, and are not subject to inherritance rules, etc. + # Permissions here are fixed, and are not subject to inheritance rules, etc. return succeed(self.defaultAccessControlList()) class CalendarUserProxyPrincipalResource (CalDAVComplianceMixIn, PermissionsMixIn, DAVPrincipalResource, DAVFile): @@ -196,12 +196,15 @@ principals.append(principal) yield principal.cacheNotifier.changed() + yield self.setGroupMemberSetPrincipals(principals) + yield self.parent.cacheNotifier.changed() + returnValue(True) + + @inlineCallbacks + def setGroupMemberSetPrincipals(self, principals): # Map the principals to UIDs. uids = [p.principalUID() for p in principals] - yield self._index().setGroupMembers(self.uid, uids) - yield self.parent.cacheNotifier.changed() - returnValue(True) ## # HTTP @@ -314,7 +317,20 @@ if self.hasEditableMembership(): # Get member UIDs from database and map to principal resources members = yield self._index().getMembers(self.uid) - returnValue([p for p in [self.pcollection.principalForUID(uid) for uid in members] if p]) + found = [] + missing = [] + for uid in members: + p = self.pcollection.principalForUID(uid) + if p: + found.append(p) + else: + missing.append(uid) + + # Clean-up ones that are missing + for uid in missing: + yield self._index().removePrincipal(uid) + + returnValue(found) else: # Fixed proxies if self.proxyType == "calendar-proxy-write": @@ -357,10 +373,10 @@ class ProxyDBMemcacher(Memcacher): def setMembers(self, guid, members): - return self.set("members:%s" % (guid,), str(",".join(members))) + return self.set("members:%s" % (str(guid),), str(",".join(members))) def setMemberships(self, guid, memberships): - return self.set("memberships:%s" % (guid,), str(",".join(memberships))) + return self.set("memberships:%s" % (str(guid),), str(",".join(memberships))) def getMembers(self, guid): def _value(value): @@ -370,7 +386,7 @@ return None else: return set() - d = self.get("members:%s" % (guid,)) + d = self.get("members:%s" % (str(guid),)) d.addCallback(_value) return d @@ -382,15 +398,15 @@ return None else: return set() - d = self.get("memberships:%s" % (guid,)) + d = self.get("memberships:%s" % (str(guid),)) d.addCallback(_value) return d def deleteMember(self, guid): - return self.delete("members:%s" % (guid,)) + return self.delete("members:%s" % (str(guid),)) def deleteMembership(self, guid): - return self.delete("memberships:%s" % (guid,)) + return self.delete("memberships:%s" % (str(guid),)) def __init__(self, path): path = os.path.join(path, CalendarUserProxyDatabase.dbFilename) @@ -435,17 +451,46 @@ @param principalUID: the UID of the group principal to remove. """ + # Need to get the members before we do the delete + members = yield self.getMembers(principalUID) + self._delete_from_db(principalUID) self._db_commit() # Update cache - members = yield self.getMembers(principalUID) if members: for member in members: yield self._memcacher.deleteMembership(member) yield self._memcacher.deleteMember(principalUID) @inlineCallbacks + def removePrincipal(self, principalUID): + """ + Remove a group membership record. + + @param principalUID: the UID of the principal to remove. + """ + + for suffix in ("calendar-proxy-read", "calendar-proxy-write",): + groupUID = "%s#%s" % (principalUID, suffix,) + self._delete_from_db(groupUID) + + # Update cache + members = yield self.getMembers(groupUID) + if members: + for member in members: + yield self._memcacher.deleteMembership(member) + yield self._memcacher.deleteMember(groupUID) + + memberships = (yield self.getMemberships(principalUID)) + for groupUID in memberships: + yield self._memcacher.deleteMember(groupUID) + + self._delete_from_db_member(principalUID) + yield self._memcacher.deleteMembership(principalUID) + self._db_commit() + + @inlineCallbacks def getMembers(self, principalUID): """ Return the list of group member UIDs for the specified principal. @@ -454,10 +499,7 @@ """ def _members(): - members = set() - for row in self._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", principalUID): - members.add(row[0]) - return members + return set([row[0] for row in self._db_execute("select MEMBER from GROUPS where GROUPNAME = :1", principalUID)]) # Pull from cache result = yield self._memcacher.getMembers(principalUID) @@ -475,10 +517,7 @@ """ def _members(): - members = set() - for row in self._db_execute("select GROUPNAME from GROUPS where MEMBER = :1", principalUID): - members.add(row[0]) - return members + return set([row[0] for row in self._db_execute("select GROUPNAME from GROUPS where MEMBER = :1", principalUID)]) # Pull from cache result = yield self._memcacher.getMemberships(principalUID) @@ -510,6 +549,14 @@ """ self._db_execute("delete from GROUPS where GROUPNAME = :1", principalUID) + def _delete_from_db_member(self, principalUID): + """ + Deletes the specified member entry from the database. + + @param principalUID: the UID of the member principal to remove. + """ + self._db_execute("delete from GROUPS where MEMBER = :1", principalUID) + def _db_version(self): """ @return: the schema version assigned to this index. Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py =================================================================== --- CalendarServer/trunk/twistedcaldav/directory/principal.py 2009-01-06 20:23:10 UTC (rev 3572) +++ CalendarServer/trunk/twistedcaldav/directory/principal.py 2009-01-07 18:15:56 UTC (rev 3573) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2006-2007 Apple Inc. All rights reserved. +# Copyright (c) 2006-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -645,6 +645,8 @@ subprincipal = self.parent.principalForUID(uid) if subprincipal: proxies.append(subprincipal) + else: + yield self._calendar_user_proxy_index().removeGroup(uid) groups.update(proxies) @@ -677,8 +679,11 @@ memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID())) for uid in memberships: subprincipal = self.parent.principalForUID(uid) - if subprincipal and subprincipal.isProxyType(read_write): - proxies.append(subprincipal.parent) + if subprincipal: + if subprincipal.isProxyType(read_write): + proxies.append(subprincipal.parent) + else: + yield self._calendar_user_proxy_index().removeGroup(uid) proxyFors.update(proxies) Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py =================================================================== --- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py 2009-01-06 20:23:10 UTC (rev 3572) +++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipaldb.py 2009-01-07 18:15:56 UTC (rev 3573) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -270,6 +270,82 @@ self.assertEqual(membershipsD, set()) @inlineCallbacks + def test_cachingDBRemoveSpecial(self): + + for processType in ("Single", "Combined",): + config.ProcessType = processType + + # Get the DB + db_path = self.mktemp() + os.mkdir(db_path) + db = CalendarUserProxyDatabase(db_path) + + # Do one insert and check the result + yield db.setGroupMembers("A", ("B", "C", "D",)) + yield db.setGroupMembers("X", ("B", "C",)) + + membershipsB = yield db.getMemberships("B") + membershipsC = yield db.getMemberships("C") + membershipsD = yield db.getMemberships("D") + + # Remove and check the result + yield db.removeGroup("A") + + membersA = yield db.getMembers("A") + membersX = yield db.getMembers("X") + membershipsB = yield db.getMemberships("B") + membershipsC = yield db.getMemberships("C") + membershipsD = yield db.getMemberships("D") + + self.assertEqual(membersA, set()) + self.assertEqual(membersX, set(("B", "C",))) + self.assertEqual(membershipsB, set("X",)) + self.assertEqual(membershipsC, set("X",)) + self.assertEqual(membershipsD, set()) + + @inlineCallbacks + def test_cachingDBRemovePrincipal(self): + + for processType in ("Single", "Combined",): + config.ProcessType = processType + + # Get the DB + db_path = self.mktemp() + os.mkdir(db_path) + db = CalendarUserProxyDatabase(db_path) + + # Do one insert and check the result + yield db.setGroupMembers("A", ("B", "C", "D",)) + yield db.setGroupMembers("X", ("B", "C",)) + + membersA = yield db.getMembers("A") + membersX = yield db.getMembers("X") + membershipsB = yield db.getMemberships("B") + membershipsC = yield db.getMemberships("C") + membershipsD = yield db.getMemberships("D") + + self.assertEqual(membersA, set(("B", "C", "D",))) + self.assertEqual(membersX, set(("B", "C",))) + self.assertEqual(membershipsB, set(("A", "X",))) + self.assertEqual(membershipsC, set(("A", "X",))) + self.assertEqual(membershipsD, set(("A",))) + + # Remove and check the result + yield db.removePrincipal("B") + + membersA = yield db.getMembers("A") + membersX = yield db.getMembers("X") + membershipsB = yield db.getMemberships("B") + membershipsC = yield db.getMemberships("C") + membershipsD = yield db.getMemberships("D") + + self.assertEqual(membersA, set(("C", "D",))) + self.assertEqual(membersX, set(("C",))) + self.assertEqual(membershipsB, set()) + self.assertEqual(membershipsC, set(("A", "X",))) + self.assertEqual(membershipsD, set(("A",),)) + + @inlineCallbacks def test_cachingDBInsertUncached(self): for processType in ("Single", "Combined",): Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py =================================================================== --- CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2009-01-06 20:23:10 UTC (rev 3572) +++ CalendarServer/trunk/twistedcaldav/directory/test/test_proxyprincipalmembers.py 2009-01-07 18:15:56 UTC (rev 3573) @@ -1,5 +1,5 @@ ## -# Copyright (c) 2005-2007 Apple Inc. All rights reserved. +# Copyright (c) 2005-2009 Apple Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,10 +18,11 @@ from twisted.web2.dav import davxml from twistedcaldav.directory.directory import DirectoryService -from twistedcaldav.directory.xmlfile import XMLDirectoryService from twistedcaldav.directory.test.test_xmlfile import xmlFile from twistedcaldav.directory.principal import DirectoryPrincipalProvisioningResource from twistedcaldav.directory.principal import DirectoryPrincipalResource +from twistedcaldav.directory.xmlaccountsparser import XMLAccountsParser +from twistedcaldav.directory.xmlfile import XMLDirectoryService import twistedcaldav.test.util @@ -76,6 +77,42 @@ return d @inlineCallbacks + def _addProxy(self, principal, subPrincipalName, proxyPrincipal): + + if isinstance(principal, tuple): + principal = self._getPrincipalByShortName(principal[0], principal[1]) + principal = principal.getChild(subPrincipalName) + members = (yield principal.groupMembers()) + + if isinstance(proxyPrincipal, tuple): + proxyPrincipal = self._getPrincipalByShortName(proxyPrincipal[0], proxyPrincipal[1]) + members.add(proxyPrincipal) + + principal.setGroupMemberSetPrincipals(members) + + @inlineCallbacks + def _removeProxy(self, recordType, recordName, subPrincipalName, proxyRecordType, proxyRecordName): + + principal = self._getPrincipalByShortName(recordType, recordName) + principal = principal.getChild(subPrincipalName) + members = (yield principal.groupMembers()) + + proxyPrincipal = self._getPrincipalByShortName(proxyRecordType, proxyRecordName) + for p in members: + if p.principalUID() == proxyPrincipal.principalUID(): + members.remove(p) + break + + principal.setGroupMemberSetPrincipals(members) + + def _clearProxy(self, principal, subPrincipalName): + + if isinstance(principal, tuple): + principal = self._getPrincipalByShortName(principal[0], principal[1]) + principal = principal.getChild(subPrincipalName) + principal.setGroupMemberSetPrincipals(set()) + + @inlineCallbacks def _proxyForTest(self, recordType, recordName, expectedProxies, read_write): principal = self._getPrincipalByShortName(recordType, recordName) proxies = (yield principal.proxyFor(read_write)) @@ -278,3 +315,121 @@ False ) + @inlineCallbacks + def test_UserProxy(self): + + for proxyType in ("calendar-proxy-read", "calendar-proxy-write"): + + yield self._addProxy( + (DirectoryService.recordType_users, "wsanchez",), + proxyType, + (DirectoryService.recordType_users, "cdaboo",), + ) + + yield self._groupMembersTest( + DirectoryService.recordType_users, "wsanchez", + proxyType, + ("Cyrus Daboo",), + ) + + yield self._addProxy( + (DirectoryService.recordType_users, "wsanchez",), + proxyType, + (DirectoryService.recordType_users, "lecroy",), + ) + + yield self._groupMembersTest( + DirectoryService.recordType_users, "wsanchez", + proxyType, + ("Cyrus Daboo", "Chris Lecroy",), + ) + + yield self._removeProxy( + DirectoryService.recordType_users, "wsanchez", + proxyType, + DirectoryService.recordType_users, "cdaboo", + ) + + yield self._groupMembersTest( + DirectoryService.recordType_users, "wsanchez", + proxyType, + ("Chris Lecroy",), + ) + + @inlineCallbacks + def test_InvalidUserProxy(self): + + for doMembershipFirst in (True, False,): + for proxyType in ("calendar-proxy-read", "calendar-proxy-write"): + principal = self._getPrincipalByShortName(DirectoryService.recordType_users, "wsanchez") + proxyGroup = principal.getChild(proxyType) + + testPrincipal = self._getPrincipalByShortName(DirectoryService.recordType_users, "cdaboo") + + fakePrincipal = self._getPrincipalByShortName(DirectoryService.recordType_users, "dreid") + fakeProxyGroup = fakePrincipal.getChild(proxyType) + + yield self._addProxy( + principal, + proxyType, + testPrincipal, + ) + members = yield proxyGroup._index().getMembers(proxyGroup.uid) + self.assertEquals(len(members), 1) + + yield self._addProxy( + fakePrincipal, + proxyType, + testPrincipal, + ) + members = yield fakeProxyGroup._index().getMembers(fakeProxyGroup.uid) + self.assertEquals(len(members), 1) + + uids = [p.principalUID() for p in (yield testPrincipal.groupMemberships())] + self.assertTrue("5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1#%s" % (proxyType,) in uids) + + memberships = yield testPrincipal._calendar_user_proxy_index().getMemberships(testPrincipal.principalUID()) + self.assertEquals(len(memberships), 2) + + yield self._addProxy( + principal, + proxyType, + fakePrincipal, + ) + members = yield proxyGroup._index().getMembers(proxyGroup.uid) + self.assertEquals(len(members), 2) + + # Remove the dreid user from the directory service + del directoryService._accounts()[DirectoryService.recordType_users]["dreid"] + + @inlineCallbacks + def _membershipTest(): + uids = [p.principalUID() for p in (yield testPrincipal.groupMemberships())] + self.assertTrue("5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1#%s" % (proxyType,) not in uids) + + memberships = yield testPrincipal._calendar_user_proxy_index().getMemberships(testPrincipal.principalUID()) + self.assertEquals(len(memberships), 1) + + @inlineCallbacks + def _membersTest(): + yield self._groupMembersTest( + DirectoryService.recordType_users, "wsanchez", + proxyType, + ("Cyrus Daboo",), + ) + members = yield proxyGroup._index().getMembers(proxyGroup.uid) + self.assertEquals(len(members), 1) + + if doMembershipFirst: + yield _membershipTest() + yield _membersTest() + else: + yield _membersTest() + yield _membershipTest() + + # Restore removed user + parser = XMLAccountsParser(directoryService.xmlFile) + directoryService._parsedAccounts = parser.items + + self._clearProxy(principal, proxyType) + self._clearProxy(fakePrincipal, proxyType)
participants (1)
-
source_changes@macosforge.org