[CalendarServer-changes] [10021] CalendarServer/branches/users/gaya/sharedgroups
source_changes at macosforge.org
source_changes at macosforge.org
Mon Nov 12 09:58:46 PST 2012
Revision: 10021
http://trac.calendarserver.org//changeset/10021
Author: gaya at apple.com
Date: 2012-11-12 09:58:46 -0800 (Mon, 12 Nov 2012)
Log Message:
-----------
Add shadow group vcard in sharee address book - first working code, cleanup to follow
Modified Paths:
--------------
CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py
CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/vcard.py
CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py
Modified: CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py 2012-11-12 17:41:18 UTC (rev 10020)
+++ CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/storebridge.py 2012-11-12 17:58:46 UTC (rev 10021)
@@ -67,7 +67,8 @@
from twistedcaldav.scheduling.caldav.resource import ScheduleInboxResource
from twistedcaldav.scheduling.implicit import ImplicitScheduler
from twistedcaldav.vcard import Component as VCard, InvalidVCardDataError
-from txdav.carddav.iaddressbookstore import GroupWithUnsharedAddressNotAllowedError
+from txdav.carddav.iaddressbookstore import GroupWithUnsharedAddressNotAllowedError, \
+ DeleteOfShadowGroupNotAllowedError
"""
Wrappers to translate between the APIs in L{txdav.caldav.icalendarstore} and
@@ -2257,10 +2258,22 @@
except GroupWithUnsharedAddressNotAllowedError:
raise HTTPError(StatusResponse(
FORBIDDEN,
- "Group vcard cannot contain addresses of unshared vcards.",)
+ "Sharee cannot add or modify group vcard such that result contains addresses of unshared vcards.",)
)
+ @inlineCallbacks
+ def http_DELETE(self, request):
+ try:
+ returnValue((yield super(AddressBookObjectResource, self).http_DELETE(request)))
+
+ except DeleteOfShadowGroupNotAllowedError:
+ raise HTTPError(StatusResponse(
+ FORBIDDEN,
+ "Sharee cannot delete group vcard shadowing shared address book",)
+ )
+
+
class _NotificationChildHelper(object):
"""
Methods for things which are like notification objects.
Modified: CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/vcard.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/vcard.py 2012-11-12 17:41:18 UTC (rev 10020)
+++ CalendarServer/branches/users/gaya/sharedgroups/twistedcaldav/vcard.py 2012-11-12 17:58:46 UTC (rev 10021)
@@ -425,7 +425,7 @@
return self._resource_kind
- def resourceMembers(self):
+ def resourceMemberAddresses(self):
"""
@return: an iterable of X-ADDRESSBOOKSERVER-MEMBER property values
"""
Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py 2012-11-12 17:41:18 UTC (rev 10020)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/datastore/sql.py 2012-11-12 17:58:46 UTC (rev 10021)
@@ -43,7 +43,8 @@
from txdav.base.propertystore.base import PropertyName
from txdav.carddav.datastore.util import validateAddressBookComponent
from txdav.carddav.iaddressbookstore import IAddressBookHome, IAddressBook, \
- IAddressBookObject, GroupWithUnsharedAddressNotAllowedError
+ IAddressBookObject, GroupWithUnsharedAddressNotAllowedError, \
+ DeleteOfShadowGroupNotAllowedError
from txdav.common.datastore.sql import CommonHome, CommonHomeChild, \
CommonObjectResource, EADDRESSBOOKTYPE, SharingMixIn
from txdav.common.datastore.sql_legacy import PostgresLegacyABIndexEmulator
@@ -271,8 +272,6 @@
returnValue((resourceID, created, modified))
-
-
@classmethod
def _memberIDsWithGroupIDsQuery(cls, groupIDs): #@NoSelf
"""
@@ -283,23 +282,25 @@
Where=aboMembers.GROUP_ID.In(Parameter("groupIDs", len(groupIDs))),
)
+
@inlineCallbacks
- def _allowedAddressBookObjectIDs(self):
+ def _allAddressBookObjectIDs(self):
"""
Get all addressbookobject resource IDs in this address book
"""
# TODO: cache in attr
# TODO: optimize
- allMemberIDs = set()
+ allMemberIDs = set() if self.owned() else set([self._resourceID, ])
examinedIDs = set()
- remainingIDs = set([self._resourceID])
+ remainingIDs = set([self._resourceID, ])
while remainingIDs:
memberRows = yield self._memberIDsWithGroupIDsQuery(remainingIDs).on(self._txn, groupIDs=remainingIDs)
allMemberIDs |= set([memberRow[0] for memberRow in memberRows])
examinedIDs |= remainingIDs
remainingIDs = allMemberIDs - examinedIDs
+ print("_allAddressBookObjectIDs:self=%s returning %s" % (self, tuple(allMemberIDs),))
returnValue(tuple(allMemberIDs))
@@ -310,14 +311,14 @@
"""
abo = schema.ADDRESSBOOK_OBJECT
return Select([abo.RESOURCE_NAME], From=abo,
- Where=abo.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs)))
+ Where=abo.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))),
)
@inlineCallbacks
def listObjectResources(self):
if self._objectNames is None:
- memberIDs = yield self._allowedAddressBookObjectIDs()
+ memberIDs = yield self._allAddressBookObjectIDs()
rows = (yield self._objectResourceNamesWithResourceIDsQuery(memberIDs).on(
self._txn, resourceIDs=memberIDs)) if memberIDs else []
self._objectNames = sorted([row[0] for row in rows])
@@ -327,7 +328,7 @@
@inlineCallbacks
def countObjectResources(self):
- returnValue(len((yield self._allowedAddressBookObjectIDs())))
+ returnValue(len((yield self._allAddressBookObjectIDs())))
class AddressBookObject(CommonObjectResource, AddressBookSharingMixIn):
@@ -354,12 +355,16 @@
def addressbook(self):
return self._addressbook
+
def kind(self):
return self._kind
+
@inlineCallbacks
def remove(self):
+ print("remove:self=%s, self._addressbook.owned()=%s" % (self, self._addressbook.owned(),))
+
aboForeignMembers = schema.ABO_FOREIGN_MEMBERS
aboMembers = schema.ABO_MEMBERS
@@ -372,30 +377,59 @@
cacheKey = queryCacher.keyForObjectWithName(shareeAddressBook._home._resourceID, shareeAddressBook._name)
yield queryCacher.invalidateAfterCommit(self._txn, cacheKey)
+
+ ownerAddressBook = self._addressbook
+
else:
+ # sharee cannot delete group representing shared address book
+ if self._resourceID == self._addressbook._resourceID:
+ print("remove:self=%s, DeleteOfShadowGroupNotAllowedError" % (self,))
+ raise DeleteOfShadowGroupNotAllowedError
+
ownerGroup, ownerAddressBook = yield self._ownerGroupAndAddressBook()
+ print("remove:self=%s, ownerGroup=%s" % (self, ownerGroup,))
if ownerGroup:
- # convert a delete of a shared group member to a remove of that member in shared group and subgroups
+ # convert a delete in a shared group to membership removes
groupIDRows = yield Select(
[aboMembers.GROUP_ID],
From=aboMembers,
Where=(aboMembers.MEMBER_ID == self._resourceID).And(
aboMembers.GROUP_ID != ownerAddressBook._resourceID),
).on(self._txn)
- groupIDs = [groupIDRow[0] for groupIDRow in groupIDRows]
- member = "urn:uuid:" + self._uid
- for groupID in groupIDs:
+ memberAddress = "urn:uuid:" + self._uid
+ for groupID in [groupIDRow[0] for groupIDRow in groupIDRows]:
groupObject = yield ownerAddressBook.objectResourceWithID(groupID)
groupComponent = yield groupObject.component()
- if member in groupComponent.resourceMembers():
- groupComponent.removeProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", member))
+ if memberAddress in groupComponent.resourceMemberAddresses():
+ groupComponent.removeProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
yield groupObject.updateDatabase(groupComponent)
returnValue(None)
# delete members table row for this object
- groupIDs = yield Delete(
+ groupIDRows = yield Select(
+ [aboMembers.GROUP_ID],
+ From=aboMembers,
+ Where=aboMembers.MEMBER_ID == self._resourceID,
+ ).on(self._txn)
+
+ memberAddress = "urn:uuid:" + self._uid
+ for groupID in [groupIDRow[0] for groupIDRow in groupIDRows]:
+ groupObject = yield ownerAddressBook.objectResourceWithID(groupID)
+ groupComponent = yield groupObject.component()
+ if memberAddress in groupComponent.resourceMemberAddresses():
+ groupComponent.removeProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+ yield groupObject.updateDatabase(groupComponent)
+
+
+
+
+ '''
+ OLD WAY
+
+ # delete members table row for this object
+ groupIDRows = yield Delete(
aboMembers,
Where=aboMembers.MEMBER_ID == self._resourceID,
Return=aboMembers.GROUP_ID
@@ -403,19 +437,21 @@
# add to foreign member table row by UID
assert self._ownerAddressBookResourceID
- for groupID in groupIDs:
- if groupID[0] != self._ownerAddressBookResourceID:
+ for groupID in [groupIDRow[0] for groupIDRow in groupIDRows]:
+ if groupID != self._ownerAddressBookResourceID:
# remove on address book, add aboForeignMembers row to local groups only
yield Insert(
- {aboForeignMembers.GROUP_ID: groupID[0],
+ {aboForeignMembers.GROUP_ID: groupID,
aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
aboForeignMembers.MEMBER_ADDRESS: "urn:uuid:" + self._uid, }
).on(self._txn)
+ '''
yield super(AddressBookObject, self).remove()
self._kind = None
self._ownerAddressBookResourceID = None
+
@classmethod
def _allWithResourceIDAnd(cls, resourceIDs, column, paramName):
"""
@@ -458,18 +494,41 @@
@return: L{self} if object exists in the DB, else C{None}
"""
- memberIDs = yield self._addressbook._allowedAddressBookObjectIDs()
+
if self._name:
+ memberIDs = yield self._addressbook._allAddressBookObjectIDs()
rows = (yield self._allWithResourceIDAndName(memberIDs).on(
self._txn, name=self._name,
resourceIDs=memberIDs,)) if memberIDs else []
elif self._uid:
+ memberIDs = yield self._addressbook._allAddressBookObjectIDs()
rows = (yield self._allWithResourceIDAndUID(memberIDs).on(
self._txn, uid=self._uid,
resourceIDs=memberIDs,)) if memberIDs else []
elif self._resourceID:
+ if (self._resourceID == self._addressbook._resourceID):
+ # Allow shadowGroup creation by resourceID only, even this is owned address book
+ memberIDs = [self._resourceID]
+ else:
+ memberIDs = yield self._addressbook._allAddressBookObjectIDs()
rows = (yield self._allWithResourceID.on(
- self._txn, resourceID=self._resourceID,)) if self._resourceID in memberIDs else []
+ self._txn, resourceID=self._resourceID,)) if (self._resourceID in memberIDs) else []
+ print("initFromStore:self=%s, self._name=%s, self._uid=%s, self._resourceID=%s, self._parentCollection._resourceID=%s rows=%s" %
+ (self, self._name, self._uid, self._resourceID, self._parentCollection._resourceID, rows))
+ '''
+ memberIDs = yield self._addressbook._allAddressBookObjectIDs()
+ if self._name:
+ rows = (yield self._allWithResourceIDAndName(memberIDs).on(
+ self._txn, name=self._name,
+ resourceIDs=memberIDs,)) if memberIDs else []
+ elif self._uid:
+ rows = (yield self._allWithResourceIDAndUID(memberIDs).on(
+ self._txn, uid=self._uid,
+ resourceIDs=memberIDs,)) if memberIDs else []
+ elif self._resourceID:
+ rows = (yield self._allWithResourceID.on(
+ self._txn, resourceID=self._resourceID,)) if (self._resourceID in memberIDs) else []
+ '''
if rows:
self._initFromRow(tuple(rows[0]))
@@ -521,15 +580,13 @@
return Select(cls._allColumns, From=obj,
Where=obj.RESOURCE_ID.In(Parameter("resourceIDs", len(resourceIDs))),)
+
@classmethod
@inlineCallbacks
def _allColumnsWithParent(cls, parent): #@NoSelf
-
- memberIDs = yield parent._allowedAddressBookObjectIDs()
-
+ memberIDs = yield parent._allAddressBookObjectIDs()
rows = (yield cls._allColumnsWithResourceIDsQuery(memberIDs).on(
parent._txn, resourceIDs=memberIDs)) if memberIDs else []
-
returnValue(rows)
@@ -544,11 +601,9 @@
@classmethod
@inlineCallbacks
def _allColumnsWithParentAndNames(cls, parent, names): #@NoSelf
- memberIDs = yield parent._allowedAddressBookObjectIDs()
-
+ memberIDs = yield parent._allAddressBookObjectIDs()
rows = (yield cls._allColumnsWithResourceIDsAndNamesQuery(memberIDs, names).on(
parent._txn, resourceIDs=memberIDs, names=names)) if memberIDs else []
-
returnValue(rows)
@@ -567,6 +622,36 @@
@inlineCallbacks
+ def _ownerGroup(self):
+ # find the owning address book
+ if not self._addressbook.owned():
+ for addressbook in (yield self._addressbook.ownerAddressBookHome().addressbooks()):
+ ownerGroup = yield addressbook.objectResourceWithID(self._addressbook._resourceID)
+ if ownerGroup:
+ returnValue(ownerGroup)
+ returnValue(None)
+
+
+ @inlineCallbacks
+ def _ownerAddressBook(self):
+ # find the owning address book
+ if self._addressbook.owned():
+ returnValue(self._addressbook)
+ else:
+ ownerAddressBook = yield self._addressbook.ownerAddressBookHome().childWithID(self._addressbook._resourceID)
+ if ownerAddressBook:
+ returnValue(ownerAddressBook)
+ else:
+ returnValue(self._ownerGroup._addressbook)
+
+
+ @inlineCallbacks
+ def _shadowGroup(self):
+ # find the owning address book
+ returnValue((yield self._addressbook.objectResourceWithID(self._addressbook._resourceID)))
+
+
+ @inlineCallbacks
def _ownerGroupAndAddressBook(self):
# find the owning address book
ownerGroup = None
@@ -581,9 +666,9 @@
if ownerGroup:
ownerAddressBook = addressbook
break
-
returnValue((ownerGroup, ownerAddressBook))
+
@inlineCallbacks
def updateDatabase(self, component, expand_until=None, reCreate=False,
inserting=False):
@@ -623,6 +708,8 @@
assert inserting or self._kind == kind # can't change kind. Should be checked in upper layers
self._kind = kind
+ print("updateDatabase:self=%s self._addressbook.=%s self._ownerAddressBookResourceID=%s" % (self, self._addressbook, self._ownerAddressBookResourceID,))
+ print("updateDatabase1:self=%s insert=%s, component=%s" % (self, inserting, component))
# For shared groups: Non owner may NOT add group members not currently in group!
# (Or it would be possible to troll for unshared vCard UIDs and make them shared.)
@@ -639,8 +726,7 @@
# get member ids
memberIDs = []
foreignMemberAddrs = []
- resourceMembers = component.resourceMembers()
- for memberAddr in resourceMembers:
+ for memberAddr in component.resourceMemberAddresses():
memberRow = []
if len(memberAddr) > len("urn:uuid:") and memberAddr.startswith("urn:uuid:"):
memberUID = memberAddr[len("urn:uuid:"):]
@@ -652,11 +738,12 @@
memberIDs.append(memberRow[0][0])
else:
foreignMemberAddrs.append(memberAddr)
+ print("updateDatabase1.1:self=%s memberAddr=%s, memberRow=%s" % (self, memberAddr, memberRow))
#in shared group, all members must be inside the shared group
if ownerGroup:
- if len(resourceMembers) != len(memberIDs) or \
- set(memberIDs) - set((yield self._addressbook._allowedAddressBookObjectIDs())):
+ if foreignMemberAddrs or \
+ set(memberIDs) - set((yield self._addressbook._allAddressBookObjectIDs())):
raise GroupWithUnsharedAddressNotAllowedError
if inserting:
@@ -671,20 +758,47 @@
md5=self._md5,
kind=self._kind,
))[0]
+ # add this vCard to shadow group and to shared group
+ groups = [ownerGroup] if ownerGroup else []
+ shadowGroup = yield ownerAddressBook.objectResourceWithID(ownerAddressBook._resourceID)
+ groups.append(shadowGroup)
+
+ memberAddress = "urn:uuid:" + self._uid
+ for group in groups:
+ groupComponent = yield group.component()
+ print("updateDatabase2:self=%s, group=%s, groupComponent=%s" % (self, group, groupComponent,))
+ if not memberAddress in groupComponent.resourceMemberAddresses():
+ groupComponent.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+ groupComponent.replaceProperty(Property("PRODID", vCardProductID))
+ print("updateDatabase2:self=%s, group=%s, groupComponent=%s" % (self, group, groupComponent,))
+ yield group.updateDatabase(groupComponent)
+
+ '''
+ OLD WAY, that does not update the abo.VCARD_TEXT & abo.MD5 of
+ owned address books
+
+ Perhaps it would be better to just fix that and not call recursively
+ because new way, above creates a shawdow group object in the owned address book,
+
if ownerGroup:
ownerGroupComponent = yield ownerGroup.component()
- member = "urn:uuid:" + self._uid
- if not member in ownerGroupComponent.resourceMembers():
- ownerGroupComponent.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", member))
+ memberAddress = "urn:uuid:" + self._uid
+ if not memberAddress in ownerGroupComponent.resourceMemberAddresses():
+ ownerGroupComponent.addProperty(Property("X-ADDRESSBOOKSERVER-MEMBER", memberAddress))
+ ownerGroupComponent.replaceProperty(Property("PRODID", vCardProductID))
yield ownerGroup.updateDatabase(ownerGroupComponent)
+ '''
+
+ '''
# add row on this address book group table
yield Insert(
{aboMembers.GROUP_ID: self._ownerAddressBookResourceID,
aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
aboMembers.MEMBER_ID: self._resourceID, }
).on(self._txn)
+ '''
# delete foreign members table row for this object
groupIDRows = yield Delete(
@@ -701,6 +815,7 @@
aboMembers.MEMBER_ID: self._resourceID, }
).on(self._txn)
+
else:
self._modified = (yield Update(
{abo.VCARD_TEXT: componentText,
@@ -721,8 +836,11 @@
memberIDsToDelete = set(currentMemberIDs) - set(memberIDs)
memberIDsToAdd = set(memberIDs) - set(currentMemberIDs)
+ print("updateDatabase3:self=%s component.resourceMemberAddresses()=%s, currentMemberIDs:%s, memberIDsToAdd:%s, memberIDsToDelete:%s" % (self, component.resourceMemberAddresses(), currentMemberIDs, memberIDsToAdd, memberIDsToDelete,))
for memberIDToDelete in memberIDsToDelete:
+ print("updateDatabase3.1:Delete((aboMembers.GROUP_ID == self._resourceID=%s).And( \
+ aboMembers.MEMBER_ID == memberIDToDelete=%s)" % (self._resourceID, memberIDToDelete,))
yield Delete(
aboMembers,
Where=((aboMembers.GROUP_ID == self._resourceID).And(
@@ -730,6 +848,8 @@
).on(self._txn)
for memberIDToAdd in memberIDsToAdd:
+ print("updateDatabase3.2:self=%s Insert(aboMembers.GROUP_ID:%s, aboMembers.ADDRESSBOOK_ID:%s, aboMembers.MEMBER_ID:%s" % (
+ self, self._resourceID, self._ownerAddressBookResourceID, memberIDToAdd))
yield Insert(
{aboMembers.GROUP_ID: self._resourceID,
aboMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
@@ -744,8 +864,12 @@
foreignMemberAddrsToDelete = set(currentForeignMemberAddrs) - set(foreignMemberAddrs)
foreignMemberAddrsToAdd = set(foreignMemberAddrs) - set(currentForeignMemberAddrs)
+ print("updateDatabase4:self=%s component.resourceMemberAddresses()=%s, currentForeignMemberAddrs:%s, foreignMemberAddrsToAdd:%s, foreignMemberAddrsToDelete:%s" %
+ (self, component.resourceMemberAddresses(), currentForeignMemberAddrs, foreignMemberAddrsToAdd, foreignMemberAddrsToDelete,))
for foreignMemberAddrToDelete in foreignMemberAddrsToDelete:
+ print("updateDatabase3.1:Delete((aboForeignMembers.GROUP_ID == self._resourceID=%s).And( \
+ aboForeignMembers.MEMBER_ADDRESS == memberIDToDelete=%s)" % (self._resourceID, foreignMemberAddrToDelete,))
yield Delete(
aboForeignMembers,
Where=((aboForeignMembers.GROUP_ID == self._resourceID).And(
@@ -753,6 +877,8 @@
).on(self._txn)
for foreignMemberAddrToAdd in foreignMemberAddrsToAdd:
+ print("updateDatabase4.2:self=%s Insert(foreignMemberAddrToAdd.GROUP_ID:%s, foreignMemberAddrToAdd.ADDRESSBOOK_ID:%s, foreignMemberAddrToAdd.MEMBER_ID:%s" % (
+ self, self._resourceID, self._ownerAddressBookResourceID, foreignMemberAddrToAdd))
yield Insert(
{aboForeignMembers.GROUP_ID: self._resourceID,
aboForeignMembers.ADDRESSBOOK_ID: self._ownerAddressBookResourceID,
@@ -802,9 +928,11 @@
def owned(self):
return True
+
def ownerHome(self):
return self._addressbook.ownerHome()
+
def notifyChanged(self):
self._addressbook.notifyChanged()
Modified: CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py
===================================================================
--- CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py 2012-11-12 17:41:18 UTC (rev 10020)
+++ CalendarServer/branches/users/gaya/sharedgroups/txdav/carddav/iaddressbookstore.py 2012-11-12 17:58:46 UTC (rev 10021)
@@ -27,6 +27,7 @@
__all__ = [
# Classes
"GroupWithUnsharedAddressNotAllowedError",
+ "DeleteOfShadowGroupNotAllowedError",
"IAddressBookTransaction",
"IAddressBookHome",
"IAddressBook",
@@ -35,8 +36,12 @@
class GroupWithUnsharedAddressNotAllowedError(CommonStoreError):
"""
- Sharee cannot add group with unshared group addresses (that could expand the scope of sharing).
+ Sharee cannot add or modify group vcard such that result contains addresses of unshared vcards.
"""
+class DeleteOfShadowGroupNotAllowedError(CommonStoreError):
+ """
+ Sharee cannot delete group vcard shadowing shared address book
+ """
class IAddressBookTransaction(ICommonTransaction):
"""
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121112/98c7a94a/attachment-0001.html>
More information about the calendarserver-changes
mailing list