[CalendarServer-changes] [12819] CalendarServer/branches/users/sagen/move2who
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 5 10:24:32 PST 2014
Revision: 12819
http://trac.calendarserver.org//changeset/12819
Author: sagen at apple.com
Date: 2014-03-05 10:24:32 -0800 (Wed, 05 Mar 2014)
Log Message:
-----------
Checking in the work-in-progress.
Modified Paths:
--------------
CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py
CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py
CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py
CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py
CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py
CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py
CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py
CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py
CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py
CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py
CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py
CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py
CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py
CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py
CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py
CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py
CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py
CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py
CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml
CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py
CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py
CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml
CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py
CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py
CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py
CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py
Added Paths:
-----------
CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/accesslog.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -103,10 +103,15 @@
else:
return uid
- uidn = convertUIDtoShortName(uidn)
- if uidz:
- uidz = convertUIDtoShortName(uidz)
+ # MOVE2WHO
+ # Better to stick the records directly on the request at
+ # an earlier point, since we can't do anything deferred
+ # in here.
+ # uidn = convertUIDtoShortName(uidn)
+ # if uidz:
+ # uidz = convertUIDtoShortName(uidz)
+
if uidn and uidz:
uid = '"%s as %s"' % (uidn, uidz,)
else:
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/provision/root.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -27,7 +27,7 @@
from txweb2.http import HTTPError, StatusResponse, RedirectResponse
from twisted.cred.error import LoginFailed, UnauthorizedLogin
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.python.reflect import namedClass
from twisted.web.xmlrpc import Proxy
from twisted.web.error import Error as WebError
@@ -110,7 +110,7 @@
def defaultAccessControlList(self):
- return config.RootResourceACL
+ return succeed(config.RootResourceACL)
@inlineCallbacks
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/push/applepush.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -820,23 +820,25 @@
def defaultAccessControlList(self):
- return davxml.ACL(
- # DAV:Read for authenticated principals
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
+ return succeed(
+ davxml.ACL(
+ # DAV:Read for authenticated principals
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
+ davxml.Protected(),
),
- davxml.Protected(),
- ),
- # DAV:Write for authenticated principals
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Write()),
+ # DAV:Write for authenticated principals
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Write()),
+ ),
+ davxml.Protected(),
),
- davxml.Protected(),
- ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tap/caldav.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1011,14 +1011,17 @@
namespace=config.GroupCaching.MemcachedPool,
useExternalProxies=config.GroupCaching.UseExternalProxies,
)
+ newGroupCacher = NewGroupCacher(DirectoryProxyClientService(None))
else:
groupCacher = None
+ newGroupCacher = None
def decorateTransaction(txn):
txn._pushDistributor = pushDistributor
txn._rootResource = result.rootResource
txn._mailRetriever = mailRetriever
txn._groupCacher = groupCacher
+ txn._newGroupCacher = newGroupCacher
store.callWithNewTransactions(decorateTransaction)
@@ -1392,7 +1395,9 @@
# Optionally enable Directory Proxy
if config.DirectoryProxy.Enabled:
- dps = DirectoryProxyServiceMaker().makeService(None)
+ dps = DirectoryProxyServiceMaker().makeService(
+ None, store=store
+ )
dps.setServiceParent(result)
def decorateTransaction(txn):
@@ -1936,14 +1941,17 @@
namespace=config.GroupCaching.MemcachedPool,
useExternalProxies=config.GroupCaching.UseExternalProxies
)
+ newGroupCacher = NewGroupCacher(DirectoryProxyClientService(None))
else:
groupCacher = None
+ newGroupCacher = None
def decorateTransaction(txn):
txn._pushDistributor = None
txn._rootResource = rootResource
txn._mailRetriever = mailRetriever
txn._groupCacher = groupCacher
+ txn._newGroupCacher = newGroupCacher
store.callWithNewTransactions(decorateTransaction)
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tap/util.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -100,7 +100,12 @@
from urllib import quote
from twisted.python.usage import UsageError
+from txdav.dps.client import DirectoryService as DirectoryProxyClientService
+from twext.who.checker import UsernamePasswordCredentialChecker
+from twext.who.checker import HTTPDigestCredentialChecker
+from twisted.cred.error import UnauthorizedLogin
+from txweb2.dav.auth import IPrincipalCredentials
log = Logger()
@@ -285,6 +290,10 @@
"""
Create an L{AggregateDirectoryService} from the given configuration.
"""
+
+ # MOVE2WHO
+ return DirectoryProxyClientService("XYZZY")
+
#
# Setup the Augment Service
#
@@ -370,7 +379,56 @@
return directory
+# MOVE2WHO -- should we move this class somewhere else?
+class PrincipalCredentialChecker(object):
+ credentialInterfaces = (IPrincipalCredentials,)
+ @inlineCallbacks
+ def requestAvatarId(self, credentials):
+ credentials = IPrincipalCredentials(credentials)
+
+ if credentials.authnPrincipal is None:
+ raise UnauthorizedLogin("No such user: %s" % (credentials.credentials.username,))
+
+ # See if record is enabledForLogin
+ if not credentials.authnPrincipal.record.isLoginEnabled():
+ raise UnauthorizedLogin(
+ "User not allowed to log in: {user}".format(
+ user=credentials.credentials.username
+ )
+ )
+
+ # Handle Kerberos as a separate behavior
+ try:
+ from twistedcaldav.authkerb import NegotiateCredentials
+ except ImportError:
+ NegotiateCredentials = None
+
+ if NegotiateCredentials and isinstance(credentials.credentials,
+ NegotiateCredentials):
+ # If we get here with Kerberos, then authentication has already succeeded
+ returnValue(
+ (
+ credentials.authnPrincipal.principalURL(),
+ credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
+ )
+ )
+ else:
+ if (yield credentials.authnPrincipal.record.verifyCredentials(credentials.credentials)):
+ returnValue(
+ (
+ credentials.authnPrincipal.principalURL(),
+ credentials.authzPrincipal.principalURL(),
+ credentials.authnPrincipal,
+ credentials.authzPrincipal,
+ )
+ )
+ else:
+ raise UnauthorizedLogin("Incorrect credentials for %s" % (credentials.credentials.username,))
+
+
def getRootResource(config, newStore, resources=None):
"""
Set up directory service and resource hierarchy based on config.
@@ -407,22 +465,27 @@
addressBookResourceClass = DirectoryAddressBookHomeProvisioningResource
directoryBackedAddressBookResourceClass = DirectoryBackedAddressBookResource
apnSubscriptionResourceClass = APNSubscriptionResource
+ principalResourceClass = DirectoryPrincipalProvisioningResource
directory = newStore.directoryService()
+ principalCollection = principalResourceClass("/principals/", directory)
+
#
# Setup the ProxyDB Service
#
- proxydbClass = namedClass(config.ProxyDBService.type)
- log.info("Configuring proxydb service of type: {cls}", cls=proxydbClass)
+ # MOVE2WHO
+ # proxydbClass = namedClass(config.ProxyDBService.type)
- try:
- calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
- except IOError:
- log.error("Could not start proxydb service")
- raise
+ # log.info("Configuring proxydb service of type: {cls}", cls=proxydbClass)
+ # try:
+ # calendaruserproxy.ProxyDBService = proxydbClass(**config.ProxyDBService.params)
+ # except IOError:
+ # log.error("Could not start proxydb service")
+ # raise
+
#
# Configure the Site and Wrappers
#
@@ -431,7 +494,9 @@
portal = Portal(auth.DavRealm())
- portal.registerChecker(directory)
+ portal.registerChecker(UsernamePasswordCredentialChecker(directory))
+ portal.registerChecker(HTTPDigestCredentialChecker(directory))
+ portal.registerChecker(PrincipalCredentialChecker())
realm = directory.realmName or ""
@@ -491,7 +556,7 @@
#
log.info("Setting up document root at: {root}", root=config.DocumentRoot)
- principalCollection = directory.principalCollection
+ # principalCollection = directory.principalCollection
if config.EnableCalDAV:
log.info("Setting up calendar collection: {cls}", cls=calendarResourceClass)
@@ -712,6 +777,7 @@
#
# Configure ancillary data
#
+ # MOVE2WHO
log.info("Configuring authentication wrapper")
overrides = {}
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/tools/principals.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -392,16 +392,17 @@
+ at inlineCallbacks
def runListPrincipals(service, rootResource, directory, store, listPrincipals):
try:
- records = list(directory.listRecords(listPrincipals))
+ records = list((yield directory.listRecords(listPrincipals)))
if records:
printRecordList(records)
else:
print("No records of type %s" % (listPrincipals,))
except UnknownRecordTypeError, e:
usage(e)
- return succeed(None)
+ returnValue(None)
@@ -411,7 +412,7 @@
for principalID in principalIDs:
# Resolve the given principal IDs to principals
try:
- principal = principalForPrincipalID(principalID, directory=directory)
+ principal = yield principalForPrincipalID(principalID, directory=directory)
except ValueError:
principal = None
@@ -525,7 +526,7 @@
@inlineCallbacks
def action_addProxy(rootResource, directory, store, principal, proxyType, *proxyIDs):
for proxyID in proxyIDs:
- proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+ proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
if proxyPrincipal is None:
print("Invalid principal ID: %s" % (proxyID,))
else:
@@ -556,7 +557,7 @@
prettyPrincipal(principal)))
memberURLs = []
for proxyID in proxyIDs:
- proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+ proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
proxyURL = proxyPrincipal.url()
memberURLs.append(davxml.HRef(proxyURL))
membersProperty = davxml.GroupMemberSet(*memberURLs)
@@ -584,7 +585,7 @@
membersProperty = (yield subPrincipal.readProperty(davxml.GroupMemberSet, None))
if membersProperty.children:
for member in membersProperty.children:
- proxyPrincipal = principalForPrincipalID(str(member), directory=directory)
+ proxyPrincipal = yield principalForPrincipalID(str(member), directory=directory)
proxies[proxyType].append(proxyPrincipal.record.guid)
returnValue((proxies['read'], proxies['write']))
@@ -594,7 +595,7 @@
@inlineCallbacks
def action_removeProxy(rootResource, directory, store, principal, *proxyIDs, **kwargs):
for proxyID in proxyIDs:
- proxyPrincipal = principalForPrincipalID(proxyID, directory=directory)
+ proxyPrincipal = yield principalForPrincipalID(proxyID, directory=directory)
if proxyPrincipal is None:
print("Invalid principal ID: %s" % (proxyID,))
else:
@@ -682,7 +683,7 @@
print("Setting auto-accept-group for %s is not allowed." % (principal,))
else:
- groupPrincipal = principalForPrincipalID(autoAcceptGroup, directory=directory)
+ groupPrincipal = yield principalForPrincipalID(autoAcceptGroup, directory=directory)
if groupPrincipal is None or groupPrincipal.record.recordType != "groups":
print("Invalid principal ID: %s" % (autoAcceptGroup,))
else:
@@ -705,9 +706,9 @@
def action_getAutoAcceptGroup(rootResource, directory, store, principal):
autoAcceptGroup = principal.getAutoAcceptGroup()
if autoAcceptGroup:
- record = directory.recordWithGUID(autoAcceptGroup)
+ record = yield directory.recordWithGUID(autoAcceptGroup)
if record is not None:
- groupPrincipal = directory.principalCollection.principalForUID(record.uid)
+ groupPrincipal = yield directory.principalCollection.principalForUID(record.uid)
if groupPrincipal is not None:
print("Auto-accept-group for %s is %s" % (
prettyPrincipal(principal),
@@ -859,16 +860,16 @@
kwargs[key] = newValue
if create:
- record = directory.createRecord(recordType, **kwargs)
+ record = yield directory.createRecord(recordType, **kwargs)
kwargs['guid'] = record.guid
else:
try:
- record = directory.updateRecord(recordType, **kwargs)
+ record = yield directory.updateRecord(recordType, **kwargs)
except NotImplementedError:
# Updating of directory information is not supported by underlying
# directory implementation, but allow augment information to be
# updated
- record = directory.recordWithGUID(kwargs["guid"])
+ record = yield directory.recordWithGUID(kwargs["guid"])
pass
augmentService = directory.serviceForRecordType(recordType).augmentService
@@ -882,7 +883,7 @@
augmentRecord.autoAcceptGroup = autoAcceptGroup
(yield augmentService.addAugmentRecords([augmentRecord]))
try:
- directory.updateRecord(recordType, **kwargs)
+ yield directory.updateRecord(recordType, **kwargs)
except NotImplementedError:
# Updating of directory information is not supported by underlying
# directory implementation, but allow augment information to be
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/webadmin/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -587,7 +587,7 @@
# Only allow administrators to access
def defaultAccessControlList(self):
- return davxml.ACL(*config.AdminACEs)
+ return succeed(davxml.ACL(*config.AdminACEs))
def etag(self):
Modified: CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/calendarserver/webcal/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -48,15 +48,17 @@
class WebCalendarResource (ReadOnlyResourceMixIn, DAVFile):
def defaultAccessControlList(self):
- return davxml.ACL(
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
+ return succeed(
+ davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
),
- davxml.Protected(),
- TwistedACLInheritable(),
- ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/customxml.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1456,6 +1456,8 @@
ResourceType.calendarproxyread = ResourceType(Principal(), Collection(), CalendarProxyRead())
ResourceType.calendarproxywrite = ResourceType(Principal(), Collection(), CalendarProxyWrite())
+ResourceType.calendarproxyreadfor = ResourceType(Principal(), Collection(), CalendarProxyReadFor())
+ResourceType.calendarproxywritefor = ResourceType(Principal(), Collection(), CalendarProxyWriteFor())
ResourceType.timezones = ResourceType(Timezones())
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/addressbook.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -65,7 +65,7 @@
DAVResource,
):
def defaultAccessControlList(self):
- return config.ProvisioningResourceACL
+ return succeed(config.ProvisioningResourceACL)
def etag(self):
@@ -93,7 +93,8 @@
super(DirectoryAddressBookHomeProvisioningResource, self).__init__()
- self.directory = IDirectoryService(directory)
+ # MOVE2WHO
+ self.directory = directory # IDirectoryService(directory)
self._url = url
self._newStore = store
@@ -103,7 +104,7 @@
#
# Create children
#
- for recordType in self.directory.recordTypes():
+ for recordType in [r.name for r in self.directory.recordTypes()]:
self.putChild(recordType, DirectoryAddressBookHomeTypeProvisioningResource(self, recordType))
self.putChild(uidsResourceName, DirectoryAddressBookHomeUIDProvisioningResource(self))
@@ -114,7 +115,7 @@
def listChildren(self):
- return self.directory.recordTypes()
+ return [r.name for r in self.directory.recordTypes()]
def principalCollections(self):
@@ -129,12 +130,13 @@
return self.directory.principalCollection.principalForRecord(record)
+ @inlineCallbacks
def homeForDirectoryRecord(self, record, request):
- uidResource = self.getChild(uidsResourceName)
+ uidResource = yield self.getChild(uidsResourceName)
if uidResource is None:
- return None
+ returnValue(None)
else:
- return uidResource.homeResourceForRecord(record, request)
+ returnValue((yield uidResource.homeResourceForRecord(record, request)))
##
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/augment.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -46,6 +46,7 @@
"automatic",
))
+
class AugmentRecord(object):
"""
Augmented directory record information
@@ -75,13 +76,14 @@
self.clonedFromDefault = False
recordTypesMap = {
- "users" : "User",
- "groups" : "Group",
- "locations" : "Location",
- "resources" : "Resource",
- "addresses" : "Address",
+ "users": "User",
+ "groups": "Group",
+ "locations": "Location",
+ "resources": "Resource",
+ "addresses": "Address",
}
+
class AugmentDB(object):
"""
Abstract base class for an augment record database.
@@ -128,7 +130,6 @@
@return: L{Deferred}
"""
-
recordType = recordTypesMap[recordType]
result = (yield self._lookupAugmentRecord(uid))
@@ -266,9 +267,9 @@
self.xmlFiles = [fullServerPath(config.DataRoot, path) for path in xmlFiles]
self.xmlFileStats = {}
for path in self.xmlFiles:
- self.xmlFileStats[path] = (0, 0) # mtime, size
+ self.xmlFileStats[path] = (0, 0) # mtime, size
- self.statSeconds = statSeconds # Don't stat more often than this value
+ self.statSeconds = statSeconds # Don't stat more often than this value
self.lastCached = 0
self.db = {}
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendar.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -65,7 +65,7 @@
DAVResource,
):
def defaultAccessControlList(self):
- return config.ProvisioningResourceACL
+ return succeed(config.ProvisioningResourceACL)
def etag(self):
@@ -91,7 +91,8 @@
super(DirectoryCalendarHomeProvisioningResource, self).__init__()
- self.directory = IDirectoryService(directory)
+ # MOVE2WHO
+ self.directory = directory # IDirectoryService(directory)
self._url = url
self._newStore = store
@@ -101,8 +102,9 @@
#
# Create children
#
- for recordType in self.directory.recordTypes():
- self.putChild(recordType, DirectoryCalendarHomeTypeProvisioningResource(self, recordType))
+ # MOVE2WHO
+ for name, recordType in [(r.name + "s", r) for r in self.directory.recordTypes()]:
+ self.putChild(name, DirectoryCalendarHomeTypeProvisioningResource(self, name, recordType))
self.putChild(uidsResourceName, DirectoryCalendarHomeUIDProvisioningResource(self))
@@ -112,7 +114,8 @@
def listChildren(self):
- return self.directory.recordTypes()
+ # MOVE2WHO
+ return [r.name + "s" for r in self.directory.recordTypes()]
def principalCollections(self):
@@ -127,12 +130,13 @@
return self.directory.principalCollection.principalForRecord(record)
+ @inlineCallbacks
def homeForDirectoryRecord(self, record, request):
- uidResource = self.getChild(uidsResourceName)
+ uidResource = yield self.getChild(uidsResourceName)
if uidResource is None:
- return None
+ returnValue(None)
else:
- return uidResource.homeResourceForRecord(record, request)
+ returnValue((yield uidResource.homeResourceForRecord(record, request)))
##
@@ -156,23 +160,25 @@
Resource which provisions calendar home collections of a specific
record type as needed.
"""
- def __init__(self, parent, recordType):
+ def __init__(self, parent, name, recordType):
"""
@param parent: the parent of this resource
@param recordType: the directory record type to provision.
"""
assert parent is not None
+ assert name is not None
assert recordType is not None
super(DirectoryCalendarHomeTypeProvisioningResource, self).__init__()
self.directory = parent.directory
+ self.name = name
self.recordType = recordType
self._parent = parent
def url(self):
- return joinURL(self._parent.url(), self.recordType)
+ return joinURL(self._parent.url(), self.name)
def listChildren(self):
@@ -203,7 +209,7 @@
def displayName(self):
- return self.recordType
+ return self.name
##
@@ -258,7 +264,7 @@
else:
# ...otherwise permissions are fixed, and are not subject to
# inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
d = getWikiACL(self, request)
d.addCallback(gotACL)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/calendaruserproxy.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -29,37 +29,40 @@
import itertools
import time
+import uuid
+from twext.python.log import Logger
+from twext.who.idirectory import RecordType as BaseRecordType
from twisted.internet.defer import succeed, inlineCallbacks, returnValue
-from txweb2 import responsecode
-from txweb2.http import HTTPError, StatusResponse
-from txdav.xml import element as davxml
-from txdav.xml.base import dav_namespace
-from txweb2.dav.util import joinURL
-from txweb2.dav.noneprops import NonePropertyStore
-
-from twext.python.log import Logger
-
+from twisted.python.modules import getModule
from twisted.web.template import XMLFile, Element, renderer
-from twisted.python.modules import getModule
-from twistedcaldav.extensions import DirectoryElement
+from twistedcaldav.config import config, fullServerPath
+from twistedcaldav.database import (
+ AbstractADBAPIDatabase, ADBAPISqliteMixin, ADBAPIPostgreSQLMixin
+)
from twistedcaldav.directory.principal import formatLink
from twistedcaldav.directory.principal import formatLinks
from twistedcaldav.directory.principal import formatPrincipals
-
from twistedcaldav.directory.util import normalizeUUID
-from twistedcaldav.config import config, fullServerPath
-from twistedcaldav.database import AbstractADBAPIDatabase, ADBAPISqliteMixin, \
- ADBAPIPostgreSQLMixin
-from twistedcaldav.extensions import DAVPrincipalResource, \
- DAVResourceWithChildrenMixin
+from twistedcaldav.extensions import (
+ DAVPrincipalResource, DAVResourceWithChildrenMixin
+)
+from twistedcaldav.extensions import DirectoryElement
from twistedcaldav.extensions import ReadOnlyWritePropertiesResourceMixIn
from twistedcaldav.memcacher import Memcacher
from twistedcaldav.resource import CalDAVComplianceMixIn
+from txdav.who.delegates import RecordType as DelegateRecordType
+from txdav.xml import element as davxml
+from txdav.xml.base import dav_namespace
+from txweb2 import responsecode
+from txweb2.dav.noneprops import NonePropertyStore
+from txweb2.dav.util import joinURL
+from txweb2.http import HTTPError, StatusResponse
thisModule = getModule(__name__)
log = Logger()
+
class PermissionsMixIn (ReadOnlyWritePropertiesResourceMixIn):
def defaultAccessControlList(self):
aces = (
@@ -86,13 +89,13 @@
for principal in config.AdminPrincipals
))
- return davxml.ACL(*aces)
+ return succeed(davxml.ACL(*aces))
def accessControlList(self, request, inheritance=True, expanding=False,
inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
@@ -119,13 +122,20 @@
record = self.resource.parent.record
resource = self.resource
parent = self.resource.parent
+ try:
+ if isinstance(record.guid, uuid.UUID):
+ guid = str(record.guid).upper()
+ else:
+ guid = record.guid
+ except AttributeError:
+ guid = ""
return tag.fillSlots(
directoryGUID=record.service.guid,
realm=record.service.realmName,
- guid=record.guid,
- recordType=record.recordType,
+ guid=guid,
+ recordType=record.recordType.name + "s", # MOVE2WHO need mapping
shortNames=record.shortNames,
- fullName=record.fullName,
+ fullName=record.displayName,
principalUID=parent.principalUID(),
principalURL=formatLink(parent.principalURL()),
proxyPrincipalUID=resource.principalUID(),
@@ -209,9 +219,13 @@
def resourceType(self):
if self.proxyType == "calendar-proxy-read":
- return davxml.ResourceType.calendarproxyread #@UndefinedVariable
+ return davxml.ResourceType.calendarproxyread # @UndefinedVariable
elif self.proxyType == "calendar-proxy-write":
- return davxml.ResourceType.calendarproxywrite #@UndefinedVariable
+ return davxml.ResourceType.calendarproxywrite # @UndefinedVariable
+ elif self.proxyType == "calendar-proxy-read-for":
+ return davxml.ResourceType.calendarproxyreadfor # @UndefinedVariable
+ elif self.proxyType == "calendar-proxy-write-for":
+ return davxml.ResourceType.calendarproxywritefor # @UndefinedVariable
else:
return super(CalendarUserProxyPrincipalResource, self).resourceType()
@@ -282,7 +296,9 @@
newUIDs.add(principal.principalUID())
# Get the old set of UIDs
- oldUIDs = (yield self._index().getMembers(self.uid))
+ # oldUIDs = (yield self._index().getMembers(self.uid))
+ oldPrincipals = yield self.groupMembers()
+ oldUIDs = [p.uid for p in oldPrincipals]
# Change membership
yield self.setGroupMemberSetPrincipals(principals)
@@ -349,7 +365,7 @@
@inlineCallbacks
- def _expandMemberUIDs(self, uid=None, relatives=None, uids=None, infinity=False):
+ def _expandMemberPrincipals(self, uid=None, relatives=None, uids=None, infinity=False):
if uid is None:
uid = self.principalUID()
if relatives is None:
@@ -360,14 +376,14 @@
if uid not in uids:
from twistedcaldav.directory.principal import DirectoryPrincipalResource
uids.add(uid)
- principal = self.pcollection.principalForUID(uid)
+ principal = yield self.pcollection.principalForUID(uid)
if isinstance(principal, CalendarUserProxyPrincipalResource):
members = yield self._directGroupMembers()
for member in members:
if member.principalUID() not in uids:
relatives.add(member)
if infinity:
- yield self._expandMemberUIDs(member.principalUID(), relatives, uids, infinity=infinity)
+ yield self._expandMemberPrincipals(member.principalUID(), relatives, uids, infinity=infinity)
elif isinstance(principal, DirectoryPrincipalResource):
if infinity:
members = yield principal.expandedGroupMembers()
@@ -378,30 +394,45 @@
returnValue(relatives)
+ def _recordTypeFromProxyType(self):
+ return {
+ "calendar-proxy-read": DelegateRecordType.readDelegateGroup,
+ "calendar-proxy-write": DelegateRecordType.writeDelegateGroup,
+ "calendar-proxy-read-for": DelegateRecordType.readDelegatorGroup,
+ "calendar-proxy-write-for": DelegateRecordType.writeDelegatorGroup,
+ }.get(self.proxyType)
+
+
@inlineCallbacks
def _directGroupMembers(self):
- # Get member UIDs from database and map to principal resources
- members = yield self._index().getMembers(self.uid)
- found = []
- for uid in members:
- p = self.pcollection.principalForUID(uid)
- if p:
- # Only principals enabledForLogin can be a delegate
- # (and groups as well)
- if (p.record.enabledForLogin or
- p.record.recordType == p.record.service.recordType_groups):
- found.append(p)
- # Make sure any outstanding deletion timer entries for
- # existing principals are removed
- yield self._index().refreshPrincipal(uid)
- else:
- self.log.warn("Delegate is missing from directory: %s" % (uid,))
+ """
+ Fault in the record representing the sub principal for this proxy type
+ (either read-only or read-write), then fault in the direct members of
+ that record.
+ """
+ memberPrincipals = []
+ record = yield self.parent.record.service.recordWithShortName(
+ self._recordTypeFromProxyType(),
+ self.parent.principalUID()
+ )
+ if record is not None:
+ memberRecords = yield record.members()
+ for record in memberRecords:
+ if record is not None:
+ principal = yield self.pcollection.principalForRecord(
+ record
+ )
+ if principal is not None:
+ if (
+ principal.record.loginAllowed or
+ principal.record.recordType is BaseRecordType.group
+ ):
+ memberPrincipals.append(principal)
+ returnValue(memberPrincipals)
- returnValue(found)
-
def groupMembers(self):
- return self._expandMemberUIDs()
+ return self._expandMemberPrincipals()
@inlineCallbacks
@@ -410,18 +441,12 @@
Return the complete, flattened set of principals belonging to this
group.
"""
- returnValue((yield self._expandMemberUIDs(infinity=True)))
+ returnValue((yield self._expandMemberPrincipals(infinity=True)))
def groupMemberships(self):
- # Get membership UIDs and map to principal resources
- d = self._index().getMemberships(self.uid)
- d.addCallback(lambda memberships: [
- p for p
- in [self.pcollection.principalForUID(uid) for uid in memberships]
- if p
- ])
- return d
+ # Unlikely to ever want to put a subprincipal into a group
+ return succeed([])
@inlineCallbacks
@@ -437,7 +462,7 @@
@return: True if principal is a proxy (of the correct type) of our parent
@rtype: C{boolean}
"""
- readWrite = self.isProxyType(True) # is read-write
+ readWrite = self.isProxyType(True) # is read-write
if principal and self.parent in (yield principal.proxyFor(readWrite)):
returnValue(True)
returnValue(False)
@@ -630,7 +655,7 @@
overdue = yield self._memcacher.checkDeletionTimer(principalUID)
- if overdue == False:
+ if overdue is False:
# Do nothing
returnValue(None)
@@ -855,9 +880,9 @@
)
if alreadyDone is None:
for (groupname, member) in (
- (yield self._db_all_values_for_sql(
- "select GROUPNAME, MEMBER from GROUPS"))
- ):
+ (yield self._db_all_values_for_sql(
+ "select GROUPNAME, MEMBER from GROUPS"))
+ ):
grouplist = groupname.split("#")
grouplist[0] = normalizeUUID(grouplist[0])
newGroupName = "#".join(grouplist)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/common.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -71,10 +71,11 @@
log.debug("No directory record with GUID %r" % (name,))
returnValue(None)
- if not getattr(record, self.enabledAttribute):
- log.debug("Directory record %r is not enabled for %s" % (
- record, self.homeResourceTypeName))
- returnValue(None)
+ # MOVE2WHO
+ # if not getattr(record, self.enabledAttribute):
+ # log.debug("Directory record %r is not enabled for %s" % (
+ # record, self.homeResourceTypeName))
+ # returnValue(None)
assert len(name) > 4, "Directory record has an invalid GUID: %r" % (
name,)
@@ -94,7 +95,7 @@
if name == "":
returnValue((self, ()))
- record = self.directory.recordWithUID(name)
+ record = yield self.directory.recordWithUID(name)
if record:
child = yield self.homeResourceForRecord(record, request)
returnValue((child, segments[1:]))
@@ -149,7 +150,7 @@
if name == "":
returnValue((self, segments[1:]))
- record = self.directory.recordWithShortName(self.recordType, name)
+ record = yield self.directory.recordWithShortName(self.recordType, name)
if record is None:
returnValue(
(NotFoundResource(principalCollections=self._parent.principalCollections()), [])
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory-principal-resource.html 2014-03-05 18:24:32 UTC (rev 12819)
@@ -11,10 +11,7 @@
GUID: <t:slot name="principalGUID"/>
Record type: <t:slot name="recordType"/>
Short names: <t:slot name="shortNames"/>
-Security Identities: <t:slot name="securityIDs"/>
Full name: <t:slot name="fullName"/>
-First name: <t:slot name="firstName"/>
-Last name: <t:slot name="lastName"/>
Email addresses:
<t:slot name="emailAddresses" />Principal UID: <t:slot name="principalUID"/>
Principal URL: <t:slot name="principalURL"/>
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/directory.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -239,7 +239,7 @@
else:
record = self.recordWithShortName(parts[2], parts[3])
- return record if record and record.enabledForCalendaring else None
+ return record if record and record.hasCalendars else None
def recordWithCachedGroupsAlias(self, recordType, alias):
@@ -531,7 +531,7 @@
)
for record in resources:
guid = record.guid
- if record.enabledForCalendaring:
+ if record.hasCalendars:
assignments.append(("%s#calendar-proxy-write" % (guid,),
record.externalProxies()))
assignments.append(("%s#calendar-proxy-read" % (guid,),
@@ -937,7 +937,8 @@
self.log.info("Retrieving list of all proxies")
# This is always a set of guids:
- delegatedGUIDs = set((yield self.proxyDB.getAllMembers()))
+ # MOVE2WHO
+ delegatedGUIDs = set() # set((yield self.proxyDB.getAllMembers()))
self.log.info("There are %d proxies" % (len(delegatedGUIDs),))
self.log.info("Retrieving group hierarchy from directory")
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directory/principal.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -28,6 +28,7 @@
"DirectoryCalendarPrincipalResource",
]
+import uuid
from urllib import unquote
from urlparse import urlparse
@@ -76,7 +77,7 @@
class PermissionsMixIn (ReadOnlyResourceMixIn):
def defaultAccessControlList(self):
- return authReadACL
+ return succeed(authReadACL)
@inlineCallbacks
@@ -94,7 +95,7 @@
else:
# ...otherwise permissions are fixed, and are not subject to
# inheritance rules, etc.
- returnValue(self.defaultAccessControlList())
+ returnValue((yield self.defaultAccessControlList()))
@@ -150,18 +151,21 @@
CalendarPrincipalCollectionResource.__init__(self, url)
DAVResourceWithChildrenMixin.__init__(self)
- self.directory = IDirectoryService(directory)
+ # MOVE2WHO
+ # self.directory = IDirectoryService(directory)
+ self.directory = directory
def __repr__(self):
return "<%s: %s %s>" % (self.__class__.__name__, self.directory, self._url)
+ @inlineCallbacks
def locateChild(self, req, segments):
- child = self.getChild(segments[0])
+ child = (yield self.getChild(segments[0]))
if child is not None:
- return (child, segments[1:])
- return (NotFoundResource(principalCollections=self.principalCollections()), ())
+ returnValue((child, segments[1:]))
+ returnValue((NotFoundResource(principalCollections=self.principalCollections()), ()))
def deadProperties(self):
@@ -174,12 +178,14 @@
return succeed(None)
+ @inlineCallbacks
def principalForShortName(self, recordType, name):
- return self.principalForRecord(self.directory.recordWithShortName(recordType, name))
+ record = (yield self.directory.recordWithShortName(recordType, name))
+ returnValue((yield self.principalForRecord(record)))
def principalForUser(self, user):
- return self.principalForShortName(DirectoryService.recordType_users, user)
+ return self.principalForShortName(self.directory.recordType.lookupByName("user"), user)
def principalForAuthID(self, user):
@@ -207,7 +213,7 @@
def principalForRecord(self, record):
if record is None or not record.enabled:
- return None
+ return succeed(None)
return self.principalForUID(record.uid)
##
@@ -281,16 +287,21 @@
#
# Create children
#
- for recordType in self.directory.recordTypes():
- self.putChild(recordType, DirectoryPrincipalTypeProvisioningResource(self, recordType))
+ # MOVE2WHO - hack: appending "s" -- need mapping
+ for name, recordType in [(r.name + "s", r) for r in self.directory.recordTypes()]:
+ self.putChild(name, DirectoryPrincipalTypeProvisioningResource(self,
+ name, recordType))
self.putChild(uidsResourceName, DirectoryPrincipalUIDProvisioningResource(self))
+ @inlineCallbacks
def principalForUID(self, uid):
- return self.getChild(uidsResourceName).getChild(uid)
+ child = (yield self.getChild(uidsResourceName))
+ returnValue((yield child.getChild(uid)))
+ @inlineCallbacks
def _principalForURI(self, uri):
scheme, netloc, path, _ignore_params, _ignore_query, _ignore_fragment = urlparse(uri)
@@ -312,56 +323,62 @@
if (host != config.ServerHostName and
host not in config.Scheduling.Options.PrincipalHostAliases):
- return None
+ returnValue(None)
if port != {
"http" : config.HTTPPort,
"https": config.SSLPort,
}[scheme]:
- return None
+ returnValue(None)
elif scheme == "urn":
if path.startswith("uuid:"):
- return self.principalForUID(path[5:])
+ returnValue((yield self.principalForUID(path[5:])))
else:
- return None
+ returnValue(None)
else:
- return None
+ returnValue(None)
if not path.startswith(self._url):
- return None
+ returnValue(None)
path = path[len(self._url) - 1:]
segments = [unquote(s) for s in path.rstrip("/").split("/")]
if segments[0] == "" and len(segments) == 3:
- typeResource = self.getChild(segments[1])
+ typeResource = yield self.getChild(segments[1])
if typeResource is not None:
- principalResource = typeResource.getChild(segments[2])
+ principalResource = yield typeResource.getChild(segments[2])
if principalResource:
- return principalResource
+ returnValue(principalResource)
- return None
+ returnValue(None)
+ @inlineCallbacks
def principalForCalendarUserAddress(self, address):
# First see if the address is a principal URI
- principal = self._principalForURI(address)
+ principal = yield self._principalForURI(address)
if principal:
- if isinstance(principal, DirectoryCalendarPrincipalResource) and principal.record.enabledForCalendaring:
- return principal
+ if (
+ isinstance(principal, DirectoryCalendarPrincipalResource) and
+ principal.record.hasCalendars
+ ):
+ returnValue(principal)
else:
# Next try looking it up in the directory
- record = self.directory.recordWithCalendarUserAddress(address)
- if record is not None and record.enabled and record.enabledForCalendaring:
- return self.principalForRecord(record)
+ record = yield self.directory.recordWithCalendarUserAddress(address)
+ if record is not None and record.hasCalendars:
+ returnValue((yield self.principalForRecord(record)))
log.debug("No principal for calendar user address: %r" % (address,))
- return None
+ returnValue(None)
+ @inlineCallbacks
def principalForRecord(self, record):
- return self.getChild(uidsResourceName).principalForRecord(record)
+ child = (yield self.getChild(uidsResourceName))
+ returnValue((yield child.principalForRecord(record)))
##
@@ -375,13 +392,14 @@
def getChild(self, name):
if name == "":
- return self
+ return succeed(self)
else:
- return self.putChildren.get(name, None)
+ return succeed(self.putChildren.get(name, None))
def listChildren(self):
- return self.directory.recordTypes()
+ # MOVE2WHO hack
+ return [r.name + "s" for r in self.directory.recordTypes()]
##
@@ -421,14 +439,14 @@
Collection resource which provisions directory principals of a
specific type as its children, indexed by short name.
"""
- def __init__(self, parent, recordType):
+ def __init__(self, parent, name, recordType):
"""
@param parent: the parent L{DirectoryPrincipalProvisioningResource}.
@param recordType: the directory record type to provision.
"""
DirectoryProvisioningResource.__init__(
self,
- joinURL(parent.principalCollectionURL(), recordType) + "/",
+ joinURL(parent.principalCollectionURL(), name) + "/",
parent.directory
)
@@ -459,7 +477,7 @@
def getChild(self, name):
if name == "":
- return self
+ return succeed(self)
else:
return self.principalForShortName(self.recordType, name)
@@ -517,16 +535,18 @@
def principalForRecord(self, record):
- if record is None or not record.enabled:
- return None
+ # MOVE2WHO
+ if record is None: # or not record.enabled:
+ return succeed(None)
- if record.enabledForCalendaring or record.enabledForAddressBooks:
+ # MOVE2WHO
+ if record.hasCalendars or record.hasContacts:
# XXX these are different features and one should not automatically
# imply the other...
principal = DirectoryCalendarPrincipalResource(self, record)
else:
principal = DirectoryPrincipalResource(self, record)
- return principal
+ return succeed(principal)
##
# Static
@@ -538,9 +558,10 @@
raise HTTPError(responsecode.NOT_FOUND)
+ @inlineCallbacks
def getChild(self, name):
if name == "":
- return self
+ returnValue(self)
if "#" in name:
# This UID belongs to a sub-principal
@@ -549,16 +570,16 @@
primaryUID = name
subType = None
- record = self.directory.recordWithUID(primaryUID)
- primaryPrincipal = self.principalForRecord(record)
+ record = (yield self.directory.recordWithUID(primaryUID))
+ primaryPrincipal = (yield self.principalForRecord(record))
if primaryPrincipal is None:
log.info("No principal found for UID: %s" % (name,))
- return None
+ returnValue(None)
if subType is None:
- return primaryPrincipal
+ returnValue(primaryPrincipal)
else:
- return primaryPrincipal.getChild(subType)
+ returnValue((yield primaryPrincipal.getChild(subType)))
def listChildren(self):
@@ -610,17 +631,31 @@
Top-level renderer in the template.
"""
record = self.resource.record
+ try:
+ if isinstance(record.guid, uuid.UUID):
+ guid = str(record.guid).upper()
+ else:
+ guid = record.guid
+ except AttributeError:
+ guid = ""
+ try:
+ emailAddresses = record.emailAddresses
+ except AttributeError:
+ emailAddresses = []
return tag.fillSlots(
directoryGUID=str(record.service.guid),
realm=str(record.service.realmName),
- principalGUID=str(record.guid),
- recordType=str(record.recordType),
+ principalGUID=guid,
+ recordType=record.recordType.name + "s", # MOVE2WHO need mapping
shortNames=",".join(record.shortNames),
- securityIDs=",".join(record.authIDs),
- fullName=str(record.fullName),
- firstName=str(record.firstName),
- lastName=str(record.lastName),
- emailAddresses=formatList(record.emailAddresses),
+ # MOVE2WHO: need this?
+ # securityIDs=",".join(record.authIDs),
+ fullName=str(record.displayName),
+ # MOVE2WHO: need this?
+ # firstName=str(record.firstName),
+ # MOVE2WHO: need this?
+ # lastName=str(record.lastName),
+ emailAddresses=formatList(emailAddresses),
principalUID=str(self.resource.principalUID()),
principalURL=formatLink(self.resource.principalURL()),
alternateURIs=formatLinks(self.resource.alternateURIs()),
@@ -697,7 +732,7 @@
"""
resource = self.resource
record = resource.record
- if record.enabledForCalendaring:
+ if record.hasCalendars:
return tag.fillSlots(
calendarUserAddresses=formatLinks(
sorted(resource.calendarUserAddresses())
@@ -715,7 +750,7 @@
"""
resource = self.resource
record = resource.record
- if record.enabledForAddressBooks:
+ if record.hasContacts:
return tag.fillSlots(
addressBookHomes=formatLinks(resource.addressBookHomeURLs())
)
@@ -750,7 +785,8 @@
(calendarserver_namespace, "first-name"),
(calendarserver_namespace, "last-name"),
(calendarserver_namespace, "email-address-set"),
- davxml.ResourceID.qname(),
+ # MOVE2WHO
+ # davxml.ResourceID.qname(),
)
cacheNotifierFactory = DisabledCacheNotifier
@@ -778,8 +814,9 @@
url = joinURL(parent.principalCollectionURL(), self.principalUID()) + slash
self._url = url
+ # MOVE2WHO - hack: just adding an "s" using recordType.name (need a mapping)
self._alternate_urls = tuple([
- joinURL(parent.parent.principalCollectionURL(), record.recordType, shortName) + slash for shortName in record.shortNames
+ joinURL(parent.parent.principalCollectionURL(), record.recordType.name+"s", shortName) + slash for shortName in record.shortNames
])
@@ -811,24 +848,27 @@
namespace, name = qname
- if qname == davxml.ResourceID.qname():
- returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
- elif namespace == calendarserver_namespace:
- if name == "first-name":
- firstName = self.record.firstName
- if firstName is not None:
- returnValue(customxml.FirstNameProperty(firstName))
- else:
- returnValue(None)
+ # MOVE2WHO -- does principal need ResourceID ?
+ # if qname == davxml.ResourceID.qname():
+ # returnValue(davxml.ResourceID(davxml.HRef.fromString("urn:uuid:%s" % (self.record.guid,))))
+ if namespace == calendarserver_namespace:
- elif name == "last-name":
- lastName = self.record.lastName
- if lastName is not None:
- returnValue(customxml.LastNameProperty(lastName))
- else:
- returnValue(None)
+ # MOVE2WHO
+ # if name == "first-name":
+ # firstName = self.record.firstName
+ # if firstName is not None:
+ # returnValue(customxml.FirstNameProperty(firstName))
+ # else:
+ # returnValue(None)
- elif name == "email-address-set":
+ # elif name == "last-name":
+ # lastName = self.record.lastName
+ # if lastName is not None:
+ # returnValue(customxml.LastNameProperty(lastName))
+ # else:
+ # returnValue(None)
+
+ if name == "email-address-set":
returnValue(customxml.EmailAddressSet(
*[customxml.EmailAddressProperty(addr) for addr in sorted(self.record.emailAddresses)]
))
@@ -867,7 +907,7 @@
def displayName(self):
- return self.record.displayName()
+ return self.record.displayName
##
# ACL
@@ -939,51 +979,40 @@
@inlineCallbacks
- def proxyFor(self, read_write, resolve_memberships=True):
+ def proxyFor(self, readWrite):
+ """
+ Returns the set of principals currently delegating to this principal
+ with the access indicated by the readWrite argument. If readWrite is
+ True, then write-access delegators are returned, otherwise the read-
+ only-access delegators are returned.
+ @param readWrite: Whether to look up read-write delegators, or
+ read-only delegators
+ @type readWrite: C{bool}
+
+ @return: A Deferred firing with a set of principals
+ """
proxyFors = set()
- if resolve_memberships:
- cache = getattr(self.record.service, "groupMembershipCache", None)
- if cache:
- log.debug("proxyFor is using groupMembershipCache")
- guids = (yield self.record.cachedGroups())
- memberships = set()
- for guid in guids:
- principal = self.parent.principalForUID(guid)
- if principal:
- memberships.add(principal)
- else:
- memberships = self._getRelatives("groups", infinity=True)
-
- for membership in memberships:
- results = (yield membership.proxyFor(read_write, False))
- proxyFors.update(results)
-
if config.EnableProxyPrincipals:
- # Get proxy group UIDs and map to principal resources
- proxies = []
- memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
- for uid in memberships:
- subprincipal = self.parent.principalForUID(uid)
- if subprincipal:
- if subprincipal.isProxyType(read_write):
- proxies.append(subprincipal.parent)
- else:
- yield self._calendar_user_proxy_index().removeGroup(uid)
+ childName = "calendar-proxy-{rw}-for".format(
+ rw=("write" if readWrite else "read")
+ )
+ proxyForGroup = yield self.getChild(childName)
+ if proxyForGroup:
+ proxyFors = yield proxyForGroup.groupMembers()
- proxyFors.update(proxies)
+ uids = set()
+ for principal in tuple(proxyFors):
+ if principal.principalUID() in uids:
+ proxyFors.remove(principal)
+ else:
+ uids.add(principal.principalUID())
- uids = set()
- for principal in tuple(proxyFors):
- if principal.principalUID() in uids:
- proxyFors.remove(principal)
- else:
- uids.add(principal.principalUID())
-
returnValue(proxyFors)
+ @inlineCallbacks
def _getRelatives(self, method, record=None, relatives=None, records=None, proxy=None, infinity=False):
if record is None:
record = self.record
@@ -994,61 +1023,62 @@
if record not in records:
records.add(record)
- for relative in getattr(record, method)():
+ for relative in (yield getattr(record, method)()):
if relative not in records:
- found = self.parent.principalForRecord(relative)
+ found = (yield self.parent.principalForRecord(relative))
if found is None:
log.error("No principal found for directory record: %r" % (relative,))
else:
if proxy:
if proxy == "read-write":
- found = found.getChild("calendar-proxy-write")
+ found = (yield found.getChild("calendar-proxy-write"))
else:
- found = found.getChild("calendar-proxy-read")
+ found = (yield found.getChild("calendar-proxy-read"))
relatives.add(found)
if infinity:
- self._getRelatives(method, relative, relatives, records,
+ yield self._getRelatives(method, relative, relatives, records,
infinity=infinity)
- return relatives
+ returnValue(relatives)
def groupMembers(self):
- return succeed(self._getRelatives("members"))
+ return self._getRelatives("members")
def expandedGroupMembers(self):
- return succeed(self._getRelatives("members", infinity=True))
+ return self._getRelatives("members", infinity=True)
@inlineCallbacks
def groupMemberships(self, infinity=False):
- cache = getattr(self.record.service, "groupMembershipCache", None)
- if cache:
- log.debug("groupMemberships is using groupMembershipCache")
- guids = (yield self.record.cachedGroups())
- groups = set()
- for guid in guids:
- principal = self.parent.principalForUID(guid)
- if principal:
- groups.add(principal)
- else:
- groups = self._getRelatives("groups", infinity=infinity)
+ # cache = getattr(self.record.service, "groupMembershipCache", None)
+ # if cache:
+ # log.debug("groupMemberships is using groupMembershipCache")
+ # guids = (yield self.record.cachedGroups())
+ # groups = set()
+ # for guid in guids:
+ # principal = yield self.parent.principalForUID(guid)
+ # if principal:
+ # groups.add(principal)
+ # else:
+ groups = yield self._getRelatives("groups", infinity=infinity)
- if config.EnableProxyPrincipals:
- # Get proxy group UIDs and map to principal resources
- proxies = []
- memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
- for uid in memberships:
- subprincipal = self.parent.principalForUID(uid)
- if subprincipal:
- proxies.append(subprincipal)
- else:
- yield self._calendar_user_proxy_index().removeGroup(uid)
+ # MOVE2WHO
+ # if config.EnableProxyPrincipals:
+ # # Get proxy group UIDs and map to principal resources
+ # proxies = []
+ # memberships = (yield self._calendar_user_proxy_index().getMemberships(self.principalUID()))
+ # for uid in memberships:
+ # subprincipal = yield self.parent.principalForUID(uid)
+ # if subprincipal:
+ # proxies.append(subprincipal)
+ # else:
+ # yield self._calendar_user_proxy_index().removeGroup(uid)
- groups.update(proxies)
+ # groups.update(proxies)
returnValue(groups)
@@ -1099,7 +1129,9 @@
def getAutoSchedule(self):
- return self.record.autoSchedule
+ # MOVE2WHO
+ return True
+ # return self.record.autoSchedule
def canAutoSchedule(self, organizer=None):
@@ -1187,18 +1219,19 @@
raise HTTPError(responsecode.NOT_FOUND)
+ @inlineCallbacks
def locateChild(self, req, segments):
- child = self.getChild(segments[0])
+ child = (yield self.getChild(segments[0]))
if child is not None:
- return (child, segments[1:])
- return (None, ())
+ returnValue((child, segments[1:]))
+ returnValue((None, ()))
def getChild(self, name):
if name == "":
- return self
+ return succeed(self)
- return None
+ return succeed(None)
def listChildren(self):
@@ -1221,7 +1254,7 @@
def addressBooksEnabled(self):
- return config.EnableCardDAV and self.record.enabledForAddressBooks
+ return config.EnableCardDAV and self.record.hasContacts
@inlineCallbacks
@@ -1288,7 +1321,7 @@
def calendarHomeURLs(self):
- if self.record.enabledForCalendaring:
+ if self.record.hasCalendars:
homeURL = self._homeChildURL(None)
else:
homeURL = ""
@@ -1318,7 +1351,7 @@
def addressBookHomeURLs(self):
- if self.record.enabledForAddressBooks:
+ if self.record.hasContacts:
homeURL = self._addressBookHomeChildURL(None)
else:
homeURL = ""
@@ -1391,22 +1424,27 @@
def getChild(self, name):
if name == "":
- return self
+ return succeed(self)
- if config.EnableProxyPrincipals and name in ("calendar-proxy-read",
- "calendar-proxy-write"):
+ if config.EnableProxyPrincipals and name in (
+ "calendar-proxy-read", "calendar-proxy-write",
+ "calendar-proxy-read-for", "calendar-proxy-write-for",
+ ):
# name is required to be str
from twistedcaldav.directory.calendaruserproxy import (
CalendarUserProxyPrincipalResource
)
- return CalendarUserProxyPrincipalResource(self, str(name))
+ return succeed(CalendarUserProxyPrincipalResource(self, str(name)))
else:
- return None
+ return succeed(None)
def listChildren(self):
if config.EnableProxyPrincipals:
- return ("calendar-proxy-read", "calendar-proxy-write")
+ return (
+ "calendar-proxy-read", "calendar-proxy-write",
+ "calendar-proxy-read-for", "calendar-proxy-write-for",
+ )
else:
return ()
@@ -1442,7 +1480,7 @@
def describe(principal):
if hasattr(principal, "record"):
- return " - %s" % (principal.record.fullName,)
+ return " - %s" % (principal.record.displayName,)
else:
return ""
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/directorybackedaddressbook.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -112,16 +112,18 @@
# DAV:Read for all authenticated principals (does not include anonymous)
accessPrincipal = davxml.Authenticated()
- return davxml.ACL(
- davxml.ACE(
- davxml.Principal(accessPrincipal),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
- davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
- ),
- davxml.Protected(),
- TwistedACLInheritable(),
- ),
+ return succeed(
+ davxml.ACL(
+ davxml.ACE(
+ davxml.Principal(accessPrincipal),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ davxml.Privilege(davxml.ReadCurrentUserPrivilegeSet())
+ ),
+ davxml.Protected(),
+ TwistedACLInheritable(),
+ ),
+ )
)
@@ -160,7 +162,7 @@
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
@inlineCallbacks
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/extensions.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -299,7 +299,7 @@
records = (yield dir.recordsMatchingTokens(tokens, context=context))
for record in records:
- resource = principalCollection.principalForRecord(record)
+ resource = yield principalCollection.principalForRecord(record)
if resource:
matchingResources.append(resource)
@@ -420,9 +420,9 @@
f.trap(HTTPError)
code = f.value.response.code
if code == responsecode.NOT_FOUND:
- log.error("Property %s was returned by listProperties() "
- "but does not exist for resource %s."
- % (name, self.resource))
+ log.error("Property {p} was returned by listProperties() "
+ "but does not exist for resource {r}.",
+ p=name, r=self.resource)
return (name, None)
if code == responsecode.UNAUTHORIZED:
return (name, accessDeniedValue)
@@ -721,7 +721,8 @@
elif name == "record-type":
if hasattr(self, "record"):
- returnValue(customxml.RecordType(self.record.recordType))
+ # MOVE2WHO -- need mapping
+ returnValue(customxml.RecordType(self.record.recordType.name + "s"))
else:
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
@@ -848,7 +849,7 @@
):
# Permissions here are fixed, and are not subject to
# inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/freebusyurl.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -102,7 +102,7 @@
davxml.Protected(),
),
)
- return davxml.ACL(*aces)
+ return succeed(davxml.ACL(*aces))
def resourceType(self):
@@ -243,7 +243,7 @@
# TODO: We should probably verify that the actual time-range is within sensible bounds (e.g. not too far in the past or future and not too long)
# Now lookup the principal details for the targeted user
- principal = self.parent.principalForRecord()
+ principal = (yield self.parent.principalForRecord())
# Pick the first mailto cu address or the first other type
cuaddr = None
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/ical.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -34,6 +34,7 @@
import itertools
import uuid
+from twisted.internet.defer import inlineCallbacks, returnValue
from twext.python.log import Logger
from txweb2.stream import IStream
from txweb2.dav.util import allDataFromStream
@@ -3239,6 +3240,7 @@
self.removeProperty(attachment)
+ @inlineCallbacks
def normalizeCalendarUserAddresses(self, lookupFunction, principalFunction,
toUUID=True):
"""
@@ -3259,7 +3261,7 @@
# Check that we can lookup this calendar user address - if not
# we cannot do anything with it
cuaddr = normalizeCUAddr(prop.value())
- name, guid, cuaddrs = lookupFunction(cuaddr, principalFunction, config)
+ name, guid, cuaddrs = yield lookupFunction(cuaddr, principalFunction, config)
if guid is None:
continue
@@ -3275,7 +3277,9 @@
if toUUID:
# Always re-write value to urn:uuid
- prop.setValue("urn:uuid:%s" % (guid,))
+ if isinstance(guid, uuid.UUID):
+ guid = unicode(guid).upper()
+ prop.setValue("urn:uuid:{guid}".format(guid=guid))
# If it is already a non-UUID address leave it be
elif cuaddr.startswith("urn:uuid:"):
@@ -3353,7 +3357,7 @@
# For VPOLL also do immediate children
if component.name() == "VPOLL":
- component.normalizeCalendarUserAddresses(lookupFunction, principalFunction, toUUID)
+ yield component.normalizeCalendarUserAddresses(lookupFunction, principalFunction, toUUID)
def allPerUserUIDs(self):
@@ -3563,15 +3567,16 @@
# Utilities
# #
+ at inlineCallbacks
def normalizeCUAddress(cuaddr, lookupFunction, principalFunction, toUUID=True):
# Check that we can lookup this calendar user address - if not
# we cannot do anything with it
- _ignore_name, guid, cuaddrs = lookupFunction(normalizeCUAddr(cuaddr), principalFunction, config)
+ _ignore_name, guid, cuaddrs = (yield lookupFunction(normalizeCUAddr(cuaddr), principalFunction, config))
if toUUID:
# Always re-write value to urn:uuid
if guid:
- return "urn:uuid:%s" % (guid,)
+ returnValue("urn:uuid:%s" % (guid,))
# If it is already a non-UUID address leave it be
elif cuaddr.startswith("urn:uuid:"):
@@ -3610,9 +3615,9 @@
# Make the change
if newaddr:
- return newaddr
+ returnValue(newaddr)
- return cuaddr
+ returnValue(cuaddr)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -863,7 +863,8 @@
home = self._newStoreObject.parentCollection().ownerHome()
else:
home = self._newStoreObject.ownerHome()
- returnValue(element.HRef(self.principalForUID(home.uid()).principalURL()))
+ principal = (yield self.principalForUID(home.uid()))
+ returnValue(element.HRef(principal.principalURL()))
else:
parent = (yield self.locateParent(request, request.urlForResource(self)))
if parent and isinstance(parent, CalDAVResource):
@@ -883,7 +884,7 @@
home = self._newStoreObject.parentCollection().ownerHome()
else:
home = self._newStoreObject.ownerHome()
- returnValue(self.principalForUID(home.uid()))
+ returnValue((yield self.principalForUID(home.uid())))
else:
parent = (yield self.locateParent(request, request.urlForResource(self)))
if parent and isinstance(parent, CalDAVResource):
@@ -933,8 +934,8 @@
return None
if 'record' in dir(self):
- if self.record.fullName:
- return self.record.fullName
+ if self.record.fullNames:
+ return self.record.fullNames[0]
elif self.record.shortNames:
return self.record.shortNames[0]
else:
@@ -1063,6 +1064,7 @@
returnValue(PerUserDataFilter(accessUID).filter(caldata))
+ # MOVE2WHO returns Deferred
def iCalendarAddressDoNormalization(self, ical):
"""
Normalize calendar user addresses in the supplied iCalendar object into their
@@ -1071,24 +1073,26 @@
@param ical: calendar object to normalize.
@type ical: L{Component}
"""
- ical.normalizeCalendarUserAddresses(normalizationLookup,
+ return ical.normalizeCalendarUserAddresses(normalizationLookup,
self.principalForCalendarUserAddress)
+ @inlineCallbacks
def principalForCalendarUserAddress(self, address):
for principalCollection in self.principalCollections():
- principal = principalCollection.principalForCalendarUserAddress(address)
+ principal = (yield principalCollection.principalForCalendarUserAddress(address))
if principal is not None:
- return principal
- return None
+ returnValue(principal)
+ returnValue(None)
+ @inlineCallbacks
def principalForUID(self, principalUID):
for principalCollection in self.principalCollections():
- principal = principalCollection.principalForUID(principalUID)
+ principal = (yield principalCollection.principalForUID(principalUID))
if principal is not None:
- return principal
- return None
+ returnValue(principal)
+ returnValue(None)
@inlineCallbacks
@@ -1874,7 +1878,7 @@
elif name == "auto-schedule-mode" and self.calendarsEnabled():
autoScheduleMode = self.getAutoScheduleMode()
- returnValue(customxml.AutoScheduleMode(autoScheduleMode if autoScheduleMode else "default"))
+ returnValue(customxml.AutoScheduleMode(autoScheduleMode.description if autoScheduleMode else "default"))
elif namespace == carddav_namespace and self.addressBooksEnabled():
if name == "addressbook-home-set":
@@ -2302,20 +2306,23 @@
# ACL
##
+ @inlineCallbacks
def owner(self, request):
- return succeed(element.HRef(self.principalForRecord().principalURL()))
+ principal = yield self.principalForRecord()
+ returnValue(element.HRef(principal.principalURL()))
def ownerPrincipal(self, request):
- return succeed(self.principalForRecord())
+ return self.principalForRecord()
def resourceOwnerPrincipal(self, request):
- return succeed(self.principalForRecord())
+ return self.principalForRecord()
+ @inlineCallbacks
def defaultAccessControlList(self):
- myPrincipal = self.principalForRecord()
+ myPrincipal = yield self.principalForRecord()
# Server may be read only
if config.EnableReadOnlyServer:
@@ -2342,12 +2349,12 @@
# Give all access to config.AdminPrincipals
aces += config.AdminACEs
- return element.ACL(*aces)
+ returnValue(element.ACL(*aces))
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
def principalCollections(self):
@@ -2555,9 +2562,10 @@
return config.Sharing.Enabled and config.Sharing.Calendars.Enabled and self.exists()
+ @inlineCallbacks
def _otherPrincipalHomeURL(self, otherUID):
- ownerPrincipal = self.principalForUID(otherUID)
- return ownerPrincipal.calendarHomeURLs()[0]
+ ownerPrincipal = (yield self.principalForUID(otherUID))
+ returnValue(ownerPrincipal.calendarHomeURLs()[0])
@inlineCallbacks
@@ -2584,8 +2592,9 @@
return self._newStoreHome.hasCalendarResourceUIDSomewhereElse(uid, ok_object._newStoreObject, mode)
+ @inlineCallbacks
def defaultAccessControlList(self):
- myPrincipal = self.principalForRecord()
+ myPrincipal = yield self.principalForRecord()
# Server may be read only
if config.EnableReadOnlyServer:
@@ -2652,7 +2661,7 @@
),
)
- return element.ACL(*aces)
+ returnValue(element.ACL(*aces))
@inlineCallbacks
@@ -2808,9 +2817,10 @@
return config.Sharing.Enabled and config.Sharing.AddressBooks.Enabled and self.exists()
+ @inlineCallbacks
def _otherPrincipalHomeURL(self, otherUID):
- ownerPrincipal = self.principalForUID(otherUID)
- return ownerPrincipal.addressBookHomeURLs()[0]
+ ownerPrincipal = (yield self.principalForUID(otherUID))
+ returnValue(ownerPrincipal.addressBookHomeURLs()[0])
@inlineCallbacks
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/scheduling_store/caldav/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -373,12 +373,14 @@
if config.Scheduling.CalDAV.OldDraftCompatibility:
privs += (davxml.Privilege(caldavxml.Schedule()),)
- return davxml.ACL(
- # CalDAV:schedule-deliver for any authenticated user
- davxml.ACE(
- davxml.Principal(davxml.Authenticated()),
- davxml.Grant(*privs),
- ),
+ return succeed(
+ davxml.ACL(
+ # CalDAV:schedule-deliver for any authenticated user
+ davxml.ACE(
+ davxml.Principal(davxml.Authenticated()),
+ davxml.Grant(*privs),
+ ),
+ )
)
@@ -532,9 +534,10 @@
return succeed(sendSchedulePrivilegeSet)
+ @inlineCallbacks
def defaultAccessControlList(self):
if config.EnableProxyPrincipals:
- myPrincipal = self.parent.principalForRecord()
+ myPrincipal = yield self.parent.principalForRecord()
privs = (
davxml.Privilege(caldavxml.ScheduleSend()),
@@ -542,16 +545,18 @@
if config.Scheduling.CalDAV.OldDraftCompatibility:
privs += (davxml.Privilege(caldavxml.Schedule()),)
- return davxml.ACL(
- # CalDAV:schedule for associated write proxies
- davxml.ACE(
- davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
- davxml.Grant(*privs),
- davxml.Protected(),
- ),
+ returnValue(
+ davxml.ACL(
+ # CalDAV:schedule for associated write proxies
+ davxml.ACE(
+ davxml.Principal(davxml.HRef(joinURL(myPrincipal.principalURL(), "calendar-proxy-write"))),
+ davxml.Grant(*privs),
+ davxml.Protected(),
+ ),
+ )
)
else:
- return super(ScheduleOutboxResource, self).defaultAccessControlList()
+ returnValue(super(ScheduleOutboxResource, self).defaultAccessControlList())
def report_urn_ietf_params_xml_ns_caldav_calendar_query(self, request, calendar_query):
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/sharing.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -792,7 +792,7 @@
Set shared state and check access control.
"""
if child._newStoreObject is not None and not child._newStoreObject.owned():
- ownerHomeURL = self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid())
+ ownerHomeURL = (yield self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid()))
ownerView = yield child._newStoreObject.ownerView()
child.setShare(joinURL(ownerHomeURL, ownerView.name()))
access = yield child._checkAccessControl()
@@ -802,6 +802,7 @@
def _otherPrincipalHomeURL(self, otherUID):
+ # Is this only meant to be overridden?
pass
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/storebridge.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1763,11 +1763,12 @@
return succeed(davPrivilegeSet)
+ @inlineCallbacks
def defaultAccessControlList(self):
"""
Only read privileges allowed for managed attachments.
"""
- myPrincipal = self.parent.principalForRecord()
+ myPrincipal = yield self.parent.principalForRecord()
read_privs = (
davxml.Privilege(davxml.Read()),
@@ -1808,12 +1809,12 @@
),
)
- return davxml.ACL(*aces)
+ returnValue(davxml.ACL(*aces))
def accessControlList(self, request, inheritance=True, expanding=False, inherited_aces=None):
# Permissions here are fixed, and are not subject to inheritance rules, etc.
- return succeed(self.defaultAccessControlList())
+ return self.defaultAccessControlList()
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_icalendar.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -19,6 +19,7 @@
import itertools
from twisted.trial.unittest import SkipTest
+from twisted.internet.defer import inlineCallbacks, succeed
from twistedcaldav.ical import Component, Property, InvalidICalendarDataError, \
normalizeCUAddress, normalize_iCalStr
@@ -32,6 +33,8 @@
from twistedcaldav.dateops import normalizeForExpand
from pycalendar.value import Value
+
+
class iCalendar (twistedcaldav.test.util.TestCase):
"""
iCalendar support tests
@@ -7497,6 +7500,7 @@
self.assertEquals(expected, ical.hasInstancesAfter(cutoff))
+ @inlineCallbacks
def test_normalizeCalendarUserAddressesFromUUID(self):
"""
Ensure mailto is preferred, followed by path form, then http form.
@@ -7520,25 +7524,27 @@
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "urn:uuid:foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo", "http://example.com/foo", "/foo")
- ),
- "urn:uuid:bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
- ),
- "urn:uuid:baz" : (
- "Baz",
- "baz",
- ("urn:uuid:baz", "http://example.com/baz")
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "urn:uuid:foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo", "http://example.com/foo", "/foo")
+ ),
+ "urn:uuid:bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
+ ),
+ "urn:uuid:baz" : (
+ "Baz",
+ "baz",
+ ("urn:uuid:baz", "http://example.com/baz")
+ ),
+ }[cuaddr]
+ )
- component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
+ yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=False)
self.assertEquals("mailto:bar at example.com",
component.getAttendeeProperty(("mailto:bar at example.com",)).value())
@@ -7548,6 +7554,7 @@
component.getAttendeeProperty(("http://example.com/baz",)).value())
+ @inlineCallbacks
def test_normalizeCalendarUserAddressesAndLocationChange(self):
"""
Ensure http(s) and /path CUA values are tucked away into the property
@@ -7573,25 +7580,27 @@
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+ )
- component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+ yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
# Location value changed
prop = component.mainComponent().getProperty("LOCATION")
@@ -7601,6 +7610,7 @@
self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
+ @inlineCallbacks
def test_normalizeCalendarUserAddressesAndLocationNoChange(self):
"""
Ensure http(s) and /path CUA values are tucked away into the property
@@ -7626,25 +7636,27 @@
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+ )
- component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+ yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
# Location value changed
prop = component.mainComponent().getProperty("LOCATION")
@@ -7654,6 +7666,7 @@
self.assertEquals(prop.parameterValue("CN"), "{Restricted} Buzz")
+ @inlineCallbacks
def test_normalizeCalendarUserAddressesAndLocationNoChangeOtherCUType(self):
"""
Ensure http(s) and /path CUA values are tucked away into the property
@@ -7679,25 +7692,27 @@
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar",)
- ),
- "http://example.com/principals/locations/buzz" : (
- "{Restricted} Buzz",
- "buzz",
- ("urn:uuid:buzz",)
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar",)
+ ),
+ "http://example.com/principals/locations/buzz" : (
+ "{Restricted} Buzz",
+ "buzz",
+ ("urn:uuid:buzz",)
+ ),
+ }[cuaddr]
+ )
- component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
+ yield component.normalizeCalendarUserAddresses(lookupFunction, None, toUUID=True)
# Location value changed
prop = component.mainComponent().getProperty("LOCATION")
@@ -8404,6 +8419,7 @@
self.assertEqual(changed, result_changed)
+ @inlineCallbacks
def test_normalizeCUAddressFromUUID(self):
"""
Ensure mailto is preferred, followed by path form, then http form.
@@ -8418,34 +8434,37 @@
)
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "urn:uuid:foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo", "http://example.com/foo", "/foo")
- ),
- "urn:uuid:bar" : (
- "Bar",
- "bar",
- ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
- ),
- "urn:uuid:baz" : (
- "Baz",
- "baz",
- ("urn:uuid:baz", "http://example.com/baz")
- ),
- "urn:uuid:buz" : (
- "Buz",
- "buz",
- ("urn:uuid:buz",)
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "urn:uuid:foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo", "http://example.com/foo", "/foo")
+ ),
+ "urn:uuid:bar" : (
+ "Bar",
+ "bar",
+ ("urn:uuid:bar", "mailto:bar at example.com", "http://example.com/bar", "/bar")
+ ),
+ "urn:uuid:baz" : (
+ "Baz",
+ "baz",
+ ("urn:uuid:baz", "http://example.com/baz")
+ ),
+ "urn:uuid:buz" : (
+ "Buz",
+ "buz",
+ ("urn:uuid:buz",)
+ ),
+ }[cuaddr]
+ )
for cuaddr, result in data:
- new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
+ new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=False)
self.assertEquals(new_cuaddr, result)
+ @inlineCallbacks
def test_normalizeCUAddressToUUID(self):
"""
Ensure http(s) and /path CUA values are tucked away into the property
@@ -8459,21 +8478,23 @@
def lookupFunction(cuaddr, ignored1, ignored2):
- return {
- "/principals/users/foo" : (
- "Foo",
- "foo",
- ("urn:uuid:foo",)
- ),
- "http://example.com/principals/users/buz" : (
- "Buz",
- "buz",
- ("urn:uuid:buz",)
- ),
- }[cuaddr]
+ return succeed(
+ {
+ "/principals/users/foo" : (
+ "Foo",
+ "foo",
+ ("urn:uuid:foo",)
+ ),
+ "http://example.com/principals/users/buz" : (
+ "Buz",
+ "buz",
+ ("urn:uuid:buz",)
+ ),
+ }[cuaddr]
+ )
for cuaddr, result in data:
- new_cuaddr = normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
+ new_cuaddr = yield normalizeCUAddress(cuaddr, lookupFunction, None, toUUID=True)
self.assertEquals(new_cuaddr, result)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/test/test_upgrade.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1536,6 +1536,7 @@
self.assertFalse(changed)
+ @inlineCallbacks
def test_normalizeCUAddrs(self):
"""
Ensure that calendar user addresses (CUAs) are cached so we can
@@ -1577,8 +1578,8 @@
directory = StubDirectory()
cuaCache = {}
- normalizeCUAddrs(normalizeEvent, directory, cuaCache)
- normalizeCUAddrs(normalizeEvent, directory, cuaCache)
+ yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
+ yield normalizeCUAddrs(normalizeEvent, directory, cuaCache)
# Ensure we only called principalForCalendarUserAddress 3 times. It
# would have been 8 times without the cuaCache.
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezoneservice.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -86,15 +86,17 @@
def defaultAccessControlList(self):
- return davxml.ACL(
- # DAV:Read for all principals (includes anonymous)
- davxml.ACE(
- davxml.Principal(davxml.All()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
+ return succeed(
+ davxml.ACL(
+ # DAV:Read for all principals (includes anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
+ davxml.Protected(),
),
- davxml.Protected(),
- ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/timezonestdservice.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -146,15 +146,17 @@
def defaultAccessControlList(self):
- return davxml.ACL(
- # DAV:Read for all principals (includes anonymous)
- davxml.ACE(
- davxml.Principal(davxml.All()),
- davxml.Grant(
- davxml.Privilege(davxml.Read()),
+ return succeed(
+ davxml.ACL(
+ # DAV:Read for all principals (includes anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
+ davxml.Grant(
+ davxml.Privilege(davxml.Read()),
+ ),
+ davxml.Protected(),
),
- davxml.Protected(),
- ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/upgrade.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -121,6 +121,7 @@
+ at inlineCallbacks
def upgradeCalendarCollection(calPath, directory, cuaCache):
errorOccurred = False
collectionUpdated = False
@@ -164,7 +165,7 @@
continue
try:
- data, fixed = normalizeCUAddrs(data, directory, cuaCache)
+ data, fixed = (yield normalizeCUAddrs(data, directory, cuaCache))
if fixed:
log.debug("Normalized CUAddrs in %s" % (resPath,))
needsRewrite = True
@@ -207,10 +208,11 @@
except:
raise
- return errorOccurred
+ returnValue(errorOccurred)
+ at inlineCallbacks
def upgradeCalendarHome(homePath, directory, cuaCache):
errorOccurred = False
@@ -229,7 +231,7 @@
rmdir(calPath)
continue
log.debug("Upgrading calendar: %s" % (calPath,))
- if not upgradeCalendarCollection(calPath, directory, cuaCache):
+ if not (yield upgradeCalendarCollection(calPath, directory, cuaCache)):
errorOccurred = True
# Change the calendar-free-busy-set xattrs of the inbox to the
@@ -254,7 +256,7 @@
log.error("Failed to upgrade calendar home %s: %s" % (homePath, e))
raise
- return errorOccurred
+ returnValue(errorOccurred)
@@ -288,9 +290,10 @@
@UpgradeOneHome.responder
+ @inlineCallbacks
def upgradeOne(self, path):
- result = upgradeCalendarHome(path, self.directory, self.cuaCache)
- return dict(succeeded=result)
+ result = yield upgradeCalendarHome(path, self.directory, self.cuaCache)
+ returnValue(dict(succeeded=result))
@@ -543,9 +546,9 @@
# Skip non-directories
continue
- if not upgradeCalendarHome(
+ if not (yield upgradeCalendarHome(
homePath, directory, cuaCache
- ):
+ )):
setError()
count += 1
@@ -564,6 +567,7 @@
+ at inlineCallbacks
def normalizeCUAddrs(data, directory, cuaCache):
"""
Normalize calendar user addresses to urn:uuid: form.
@@ -583,23 +587,24 @@
"""
cal = Component.fromString(data)
+ @inlineCallbacks
def lookupFunction(cuaddr, principalFunction, config):
# Return cached results, if any.
if cuaddr in cuaCache:
- return cuaCache[cuaddr]
+ returnValue(cuaCache[cuaddr])
- result = normalizationLookup(cuaddr, principalFunction, config)
+ result = yield normalizationLookup(cuaddr, principalFunction, config)
# Cache the result
cuaCache[cuaddr] = result
- return result
+ returnValue(result)
- cal.normalizeCalendarUserAddresses(lookupFunction,
+ yield cal.normalizeCalendarUserAddresses(lookupFunction,
directory.principalForCalendarUserAddress)
newData = str(cal)
- return newData, not newData == data
+ returnValue(newData, not newData == data)
@@ -997,7 +1002,9 @@
"""
Execute the step.
"""
- return self.doUpgrade()
+ return succeed(None)
+ # MOVE2WHO
+ # return self.doUpgrade()
Modified: CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/twistedcaldav/util.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -23,6 +23,7 @@
from hashlib import md5, sha1
from twisted.internet import ssl, reactor
+from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.web import client
from twisted.python import failure
from twext.python.log import Logger
@@ -495,6 +496,7 @@
+ at inlineCallbacks
def normalizationLookup(cuaddr, principalFunction, config):
"""
Lookup function to be passed to ical.normalizeCalendarUserAddresses.
@@ -503,13 +505,13 @@
principal for the cuaddr.
"""
try:
- principal = principalFunction(cuaddr)
+ principal = yield principalFunction(cuaddr)
except Exception, e:
log.debug("Lookup of %s failed: %s" % (cuaddr, e))
principal = None
if principal is None:
- return (None, None, None)
+ returnValue((None, None, None))
else:
rec = principal.record
@@ -520,9 +522,9 @@
# to single-quotes.
fullName = rec.fullName.replace('"', "'")
- cuas = principal.record.calendarUserAddresses
+ cuas = principal.record.calendarUserAddresses()
- return (fullName, rec.guid, cuas)
+ returnValue((fullName, rec.guid, cuas))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/caldav/scheduler.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -85,13 +85,14 @@
))
+ @inlineCallbacks
def checkOriginator(self):
"""
Check the validity of the Originator header. Extract the corresponding principal.
"""
# Verify that Originator is a valid calendar user
- originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
+ originatorPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
if originatorPrincipal is None:
# Local requests MUST have a principal.
log.error("Could not find principal for originator: %s" % (self.originator,))
@@ -122,7 +123,7 @@
results = []
for recipient in self.recipients:
# Get the principal resource for this recipient
- principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
+ principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
# If no principal we may have a remote recipient but we should check whether
# the address is one that ought to be on our server and treat that as a missing
@@ -161,7 +162,7 @@
# Verify that the ORGANIZER's cu address maps to a valid user
organizer = self.calendar.getOrganizer()
if organizer:
- organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
+ organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
if organizerPrincipal:
if organizerPrincipal.calendarsEnabled():
@@ -225,6 +226,7 @@
))
+ @inlineCallbacks
def checkAttendeeAsOriginator(self):
"""
Check the validity of the ATTENDEE value as this is the originator of the iTIP message.
@@ -232,7 +234,7 @@
"""
# Attendee's Outbox MUST be the request URI
- attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
+ attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
if attendeePrincipal:
if self.doingPOST is not None and attendeePrincipal.uid != self.originator_uid:
log.error("ATTENDEE in calendar data does not match owner of Outbox: %s" % (self.calendar,))
@@ -257,11 +259,11 @@
# Prevent spoofing of ORGANIZER with specific METHODs when local
if self.isiTIPRequest:
- self.checkOrganizerAsOriginator()
+ return self.checkOrganizerAsOriginator()
# Prevent spoofing when doing reply-like METHODs
else:
- self.checkAttendeeAsOriginator()
+ return self.checkAttendeeAsOriginator()
def finalChecks(self):
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/freebusy.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -212,12 +212,12 @@
# TODO: actually we by pass altogether by assuming anyone can check anyone else's freebusy
# May need organizer principal
- organizer_record = calresource.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
+ organizer_record = (yield calresource.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
organizer_uid = organizer_record.uid if organizer_record else ""
# Free busy is per-user
attendee_uid = calresource.viewerHome().uid()
- attendee_record = calresource.directoryService().recordWithUID(attendee_uid)
+ attendee_record = yield calresource.directoryService().recordWithUID(attendee_uid)
# Get the timezone property from the collection.
tz = calresource.getTimezone()
@@ -237,7 +237,7 @@
authz_record = organizer_record
if hasattr(calresource._txn, "_authz_uid") and calresource._txn._authz_uid != organizer_uid:
authz_uid = calresource._txn._authz_uid
- authz_record = calresource.directoryService().recordWithUID(authz_uid)
+ authz_record = yield calresource.directoryService().recordWithUID(unicode(authz_uid))
# Check if attendee is also the organizer or the delegate doing the request
if attendee_uid in (organizer_uid, authz_uid):
@@ -335,7 +335,7 @@
if excludeuid:
# See if we have a UID match
if (excludeuid == uid):
- test_record = calresource.directoryService().recordWithCalendarUserAddress(test_organizer) if test_organizer else None
+ test_record = (yield calresource.directoryService().recordWithCalendarUserAddress(test_organizer)) if test_organizer else None
test_uid = test_record.uid if test_record else ""
# Check that ORGANIZER's match (security requirement)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/imip/inbound.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -329,7 +329,7 @@
toAddr = organizer[7:]
elif organizer.startswith("urn:uuid:"):
guid = organizer[9:]
- record = self.directory.recordWithGUID(guid)
+ record = yield self.directory.recordWithGUID(guid)
if record and record.emailAddresses:
toAddr = list(record.emailAddresses)[0]
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/implicit.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -297,7 +297,7 @@
organizer_scheduling = (yield self.isOrganizerScheduling())
if organizer_scheduling:
self.state = "organizer"
- elif self.isAttendeeScheduling():
+ elif (yield self.isAttendeeScheduling()):
self.state = "attendee"
elif self.organizer:
# There is an ORGANIZER that is not this user but no ATTENDEE property for
@@ -365,7 +365,7 @@
# Get some useful information from the calendar
yield self.extractCalendarData()
- self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+ self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
# Originator is the organizer in this case
@@ -447,7 +447,7 @@
self.calendar = calendar_old
yield self.extractCalendarData()
- self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+ self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
# Originator is the organizer in this case
@@ -479,7 +479,7 @@
# Get some useful information from the calendar
yield self.extractCalendarData()
- self.attendeePrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
+ self.attendeePrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
self.originator = self.attendee = self.attendeePrincipal.canonicalCalendarUserAddress()
result = (yield self.scheduleWithOrganizer())
@@ -491,7 +491,7 @@
def extractCalendarData(self):
# Get the originator who is the owner of the calendar resource being modified
- self.originatorPrincipal = self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
+ self.originatorPrincipal = yield self.calendar_home.directoryService().recordWithUID(self.calendar_home.uid())
# Pick the canonical CUA:
self.originator = self.originatorPrincipal.canonicalCalendarUserAddress()
@@ -555,7 +555,7 @@
returnValue(False)
# Organizer must map to a valid principal
- self.organizerPrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
+ self.organizerPrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(self.organizer)
self.organizerAddress = (yield addressmapping.mapper.getCalendarUser(self.organizer, self.organizerPrincipal))
if not self.organizerPrincipal:
returnValue(False)
@@ -567,21 +567,22 @@
returnValue(True)
+ @inlineCallbacks
def isAttendeeScheduling(self):
# First must have organizer property
if not self.organizer:
- return False
+ returnValue(False)
# Check to see whether any attendee is the owner
for attendee in self.attendees:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
if attendeePrincipal and attendeePrincipal.uid == self.calendar_home.uid():
self.attendee = attendee
self.attendeePrincipal = attendeePrincipal
- return True
+ returnValue(True)
- return False
+ returnValue(False)
def makeScheduler(self):
@@ -1015,7 +1016,7 @@
if attendee.parameterValue("SCHEDULE-AGENT", "SERVER").upper() == "CLIENT":
cuaddr = attendee.value()
if cuaddr not in coerced:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(cuaddr)
attendeeAddress = (yield addressmapping.mapper.getCalendarUser(cuaddr, attendeePrincipal))
local_attendee = type(attendeeAddress) in (LocalCalendarUser, OtherServerCalendarUser,)
coerced[cuaddr] = local_attendee
@@ -1078,7 +1079,7 @@
# Handle split by not scheduling local attendees
if self.split_details is not None:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
if type(attendeeAddress) is LocalCalendarUser:
continue
@@ -1135,7 +1136,7 @@
# Handle split by not scheduling local attendees
if self.split_details is not None:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
if type(attendeeAddress) is LocalCalendarUser:
continue
@@ -1195,7 +1196,7 @@
# Handle split by not scheduling local attendees
if self.split_details is not None:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
if type(attendeeAddress) is LocalCalendarUser:
continue
@@ -1260,7 +1261,7 @@
# Handle split by not scheduling local attendees
if self.split_details is not None:
- attendeePrincipal = self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
+ attendeePrincipal = yield self.calendar_home.directoryService().recordWithCalendarUserAddress(attendee)
attendeeAddress = (yield addressmapping.mapper.getCalendarUser(attendee, attendeePrincipal))
if type(attendeeAddress) is LocalCalendarUser:
continue
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/delivery.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -228,7 +228,7 @@
# Loop over at most 3 redirects
ssl, host, port, path = self.server.details()
for _ignore in xrange(3):
- self._prepareRequest(host, port)
+ yield self._prepareRequest(host, port)
response = (yield self._processRequest(ssl, host, port, path))
if response.code not in (responsecode.MOVED_PERMANENTLY, responsecode.TEMPORARY_REDIRECT,):
break
@@ -334,16 +334,18 @@
returnValue(iostr.getvalue())
+ @inlineCallbacks
def _prepareRequest(self, host, port):
"""
Setup the request for sending. We might need to do this several times
whilst following redirects.
"""
- component, method = self._prepareData()
- self._prepareHeaders(host, port, component, method)
+ component, method = (yield self._prepareData())
+ yield self._prepareHeaders(host, port, component, method)
+ @inlineCallbacks
def _prepareHeaders(self, host, port, component, method):
"""
Always generate a new set of headers because the Host may varying during redirects,
@@ -357,7 +359,7 @@
# The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
originator = self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee
if self.server.unNormalizeAddresses:
- originator = normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
+ originator = yield normalizeCUAddress(originator, normalizationLookup, self.scheduler.txn.directoryService().recordWithCalendarUserAddress, toUUID=False)
self.headers.addRawHeader("Originator", utf8String(originator))
self.sign_headers.append("Originator")
@@ -399,6 +401,7 @@
self.sign_headers.append("Authorization")
+ @inlineCallbacks
def _prepareData(self):
"""
Prepare data via normalization etc. Only need to do this once even when
@@ -411,7 +414,7 @@
normalizedCalendar = self.scheduler.calendar.duplicate()
self.original_organizer = normalizedCalendar.getOrganizer()
if self.server.unNormalizeAddresses:
- normalizedCalendar.normalizeCalendarUserAddresses(
+ yield normalizedCalendar.normalizeCalendarUserAddresses(
normalizationLookup,
self.scheduler.txn.directoryService().recordWithCalendarUserAddress,
toUUID=False)
@@ -423,12 +426,12 @@
component = normalizedCalendar.mainType()
method = normalizedCalendar.propertyValue("METHOD")
self.data = str(normalizedCalendar)
- return component, method
+ returnValue(component, method)
else:
cal = Component.fromString(self.data)
component = cal.mainType()
method = cal.propertyValue("METHOD")
- return component, method
+ returnValue(component, method)
@inlineCallbacks
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -353,11 +353,13 @@
davxml.Privilege(caldavxml.ScheduleDeliver()),
)
- return davxml.ACL(
- # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
- davxml.ACE(
- davxml.Principal(davxml.All()),
- davxml.Grant(*privs),
- davxml.Protected(),
- ),
+ return succeed(
+ davxml.ACL(
+ # DAV:Read, CalDAV:schedule-deliver for all principals (includes anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
+ davxml.Grant(*privs),
+ davxml.Protected(),
+ ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/ischedule/scheduler.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -186,7 +186,7 @@
# Normalize recipient addresses
results = []
for recipient in recipients:
- normalized = normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
+ normalized = yield normalizeCUAddress(recipient, normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
self.recipientsNormalizationMap[normalized] = recipient
results.append(normalized)
recipients = results
@@ -205,7 +205,7 @@
if not self.checkForFreeBusy():
# Need to normalize the calendar data and recipient values to keep those in sync,
# as we might later try to match them
- self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
+ return self.calendar.normalizeCalendarUserAddresses(normalizationLookup, self.txn.directoryService().recordWithCalendarUserAddress)
def checkAuthorization(self):
@@ -226,7 +226,7 @@
"""
# For remote requests we do not allow the originator to be a local user or one within our domain.
- originatorPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.originator)
+ originatorPrincipal = (yield self.txn.directoryService().recordWithCalendarUserAddress(self.originator))
localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.originator))
if originatorPrincipal or localUser:
if originatorPrincipal.thisServer():
@@ -367,7 +367,7 @@
# Verify that the ORGANIZER's cu address does not map to a valid user
organizer = self.calendar.getOrganizer()
if organizer:
- organizerPrincipal = self.txn.directoryService().recordWithCalendarUserAddress(organizer)
+ organizerPrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(organizer)
if organizerPrincipal:
if organizerPrincipal.thisServer():
log.error("Invalid ORGANIZER in calendar data: %s" % (self.calendar,))
@@ -408,7 +408,7 @@
"""
# Attendee cannot be local.
- attendeePrincipal = self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
+ attendeePrincipal = yield self.txn.directoryService().recordWithCalendarUserAddress(self.attendee)
if attendeePrincipal:
if attendeePrincipal.thisServer():
log.error("Invalid ATTENDEE in calendar data: %s" % (self.calendar,))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/processing.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -36,6 +36,7 @@
from txdav.caldav.datastore.scheduling.work import ScheduleRefreshWork, \
ScheduleAutoReplyWork
from txdav.caldav.icalendarstore import ComponentUpdateState, ComponentRemoveState
+from txdav.who.idirectory import AutoScheduleMode
import collections
import hashlib
@@ -58,6 +59,8 @@
log = Logger()
+
+
class ImplicitProcessorException(Exception):
def __init__(self, msg):
@@ -604,19 +607,28 @@
@param calendar: the iTIP message to process
@type calendar: L{Component}
@param automode: the auto-schedule mode for the recipient
- @type automode: C{str}
+ @type automode: L{txdav.who.idirectory.AutoScheduleMode}
@return: C{tuple} of C{bool}, C{bool}, C{str} indicating whether changes were made, whether the inbox item
should be added, and the new PARTSTAT.
"""
-
# First ignore the none mode
- if automode == "none":
+ if automode == AutoScheduleMode.none:
returnValue((False, True, "",))
- elif not automode or automode == "default":
- automode = config.Scheduling.Options.AutoSchedule.DefaultMode
+ elif not automode:
+ automode = {
+ "none": AutoScheduleMode.none,
+ "accept-always": AutoScheduleMode.accept,
+ "decline-always": AutoScheduleMode.decline,
+ "accept-if-free": AutoScheduleMode.acceptIfFree,
+ "decline-if-busy": AutoScheduleMode.declineIfBusy,
+ "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ }.get(
+ config.Scheduling.Options.AutoSchedule.DefaultMode,
+ "automatic"
+ )
- log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode,))
+ log.debug("ImplicitProcessing - recipient '%s' processing UID: '%s' - checking for auto-reply with mode: %s" % (self.recipient.cuaddr, self.uid, automode.name,))
cuas = self.recipient.principal.calendarUserAddresses
@@ -704,13 +716,19 @@
partstat_counts = collections.defaultdict(int)
for instance in instances.instances.itervalues():
if instance.partstat == "NEEDS-ACTION" and instance.active:
- if automode == "accept-always":
+ if automode == AutoScheduleMode.accept:
freePartstat = busyPartstat = "ACCEPTED"
- elif automode == "decline-always":
+ elif automode == AutoScheduleMode.decline:
freePartstat = busyPartstat = "DECLINED"
else:
- freePartstat = "ACCEPTED" if automode in ("accept-if-free", "automatic",) else "NEEDS-ACTION"
- busyPartstat = "DECLINED" if automode in ("decline-if-busy", "automatic",) else "NEEDS-ACTION"
+ freePartstat = "ACCEPTED" if automode in (
+ AutoScheduleMode.acceptIfFree,
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ ) else "NEEDS-ACTION"
+ busyPartstat = "DECLINED" if automode in (
+ AutoScheduleMode.declineIfBusy,
+ AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ ) else "NEEDS-ACTION"
instance.partstat = freePartstat if instance.free else busyPartstat
partstat_counts[instance.partstat] += 1
@@ -901,7 +919,7 @@
# We only need to fix data that already exists
if recipient_resource is not None:
- if originator_calendar.mainType() != None:
+ if originator_calendar.mainType() is not None:
yield self.writeCalendarResource(None, recipient_resource, originator_calendar)
else:
yield self.deleteCalendarResource(recipient_resource)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/scheduler.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -162,7 +162,7 @@
"""
self.calendar = calendar
- self.preProcessCalendarData()
+ yield self.preProcessCalendarData()
if self.logItems is not None:
self.logItems["recipients"] = len(recipients)
@@ -550,7 +550,7 @@
results = []
for recipient in self.recipients:
# Get the principal resource for this recipient
- principal = self.txn.directoryService().recordWithCalendarUserAddress(recipient)
+ principal = yield self.txn.directoryService().recordWithCalendarUserAddress(recipient)
# If no principal we may have a remote recipient but we should check whether
# the address is one that ought to be on our server and treat that as a missing
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/scheduling/work.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -189,7 +189,7 @@
try:
home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
resource = (yield home.objectResourceWithID(self.resourceID))
- organizerPrincipal = home.directoryService().recordWithUID(home.uid())
+ organizerPrincipal = yield home.directoryService().recordWithUID(home.uid())
organizer = organizerPrincipal.canonicalCalendarUserAddress()
calendar_old = Component.fromString(self.icalendarTextOld) if self.icalendarTextOld else None
calendar_new = Component.fromString(self.icalendarTextNew) if self.icalendarTextNew else None
@@ -311,7 +311,7 @@
try:
home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
resource = (yield home.objectResourceWithID(self.resourceID))
- attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+ attendeePrincipal = yield home.directoryService().recordWithUID(home.uid())
attendee = attendeePrincipal.canonicalCalendarUserAddress()
calendar = (yield resource.componentForUser())
organizer = calendar.validOrganizerForScheduling()
@@ -336,6 +336,7 @@
self._dequeued()
except Exception, e:
+ # FIXME: calendar may not be set here!
log.debug("ScheduleReplyWork - exception ID: {id}, UID: '{uid}', {err}", id=self.workID, uid=calendar.resourceUID(), err=str(e))
raise
except:
@@ -381,7 +382,7 @@
try:
home = (yield self.transaction.calendarHomeWithResourceID(self.homeResourceID))
- attendeePrincipal = home.directoryService().recordWithUID(home.uid())
+ attendeePrincipal = yield home.directoryService().recordWithUID(home.uid())
attendee = attendeePrincipal.canonicalCalendarUserAddress()
calendar = Component.fromString(self.icalendarText)
organizer = calendar.validOrganizerForScheduling()
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/sql.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1915,14 +1915,14 @@
# Normalize the calendar user addresses once we know we have valid
# calendar data
- component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
+ yield component.normalizeCalendarUserAddresses(normalizationLookup, self.directoryService().recordWithCalendarUserAddress)
# Possible timezone stripping
if config.EnableTimezonesByReference:
component.stripKnownTimezones()
# Check location/resource organizer requirement
- self.validLocationResourceOrganizer(component, inserting, internal_state)
+ yield self.validLocationResourceOrganizer(component, inserting, internal_state)
# Check access
if config.EnablePrivateEvents:
@@ -1986,13 +1986,14 @@
raise TooManyAttendeesError("Attendee list size %d is larger than allowed limit %d" % (attendeeListLength, config.MaxAttendeesPerInstance))
+ @inlineCallbacks
def validLocationResourceOrganizer(self, component, inserting, internal_state):
"""
If the calendar owner is a location or resource, check whether an ORGANIZER property is required.
"""
if internal_state == ComponentUpdateState.NORMAL:
- originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+ originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
cutype = originatorPrincipal.getCUType() if originatorPrincipal is not None else "INDIVIDUAL"
organizer = component.getOrganizer()
@@ -2009,10 +2010,10 @@
# Find current principal and update modified by details
if hasattr(self._txn, "_authz_uid"):
- authz = self.directoryService().recordWithUID(self._txn._authz_uid)
+ authz = yield self.directoryService().recordWithUID(self._txn._authz_uid)
prop = Property("X-CALENDARSERVER-MODIFIED-BY", authz.canonicalCalendarUserAddress())
prop.setParameter("CN", authz.displayName())
- for candidate in authz.calendarUserAddresses:
+ for candidate in authz.calendarUserAddresses():
if candidate.startswith("mailto:"):
prop.setParameter("EMAIL", candidate[7:])
break
@@ -2108,7 +2109,7 @@
log.debug("Organizer and attendee properties were entirely removed by the client. Restoring existing properties.")
# Get the originator who is the owner of the calendar resource being modified
- originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+ originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
originatorAddresses = originatorPrincipal.calendarUserAddresses
for component in calendar.subcomponents():
@@ -2145,7 +2146,7 @@
log.debug("Sync COMPLETED property change.")
# Get the originator who is the owner of the calendar resource being modified
- originatorPrincipal = self.calendar().ownerHome().directoryRecord()
+ originatorPrincipal = yield self.calendar().ownerHome().directoryRecord()
originatorAddresses = originatorPrincipal.calendarUserAddresses
for component in calendar.subcomponents():
@@ -2264,6 +2265,7 @@
self._componentChanged = True
+ @inlineCallbacks
def addStructuredLocation(self, component):
"""
Scan the component for ROOM attendees; if any are associated with an
@@ -2277,12 +2279,12 @@
value = attendee.value()
if value.startswith("urn:uuid:"):
guid = value[9:]
- loc = self.directoryService().recordWithGUID(guid)
+ loc = yield self.directoryService().recordWithGUID(guid)
if loc is not None:
guid = loc.extras.get("associatedAddress",
None)
if guid is not None:
- addr = self.directoryService().recordWithGUID(guid)
+ addr = yield self.directoryService().recordWithGUID(guid)
if addr is not None:
street = addr.extras.get("streetAddress", "")
geo = addr.extras.get("geo", "")
@@ -2539,7 +2541,7 @@
self.processAlarms(component, inserting)
# Process structured location
- self.addStructuredLocation(component)
+ yield self.addStructuredLocation(component)
# Do scheduling
implicit_result = (yield self.doImplicitScheduling(component, inserting, internal_state))
@@ -3760,9 +3762,9 @@
raise InvalidSplit()
# Cannot be attendee
- ownerPrincipal = self.calendar().ownerHome().directoryRecord()
+ ownerPrincipal = yield self.calendar().ownerHome().directoryRecord()
organizer = component.getOrganizer()
- organizerPrincipal = self.directoryService().recordWithCalendarUserAddress(organizer) if organizer else None
+ organizerPrincipal = (yield self.directoryService().recordWithCalendarUserAddress(organizer)) if organizer else None
if organizer is not None and organizerPrincipal.uid != ownerPrincipal.uid:
raise InvalidSplit()
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/test/util.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -16,7 +16,7 @@
##
from twisted.trial.unittest import TestCase
from twext.python.clsprop import classproperty
-from twisted.internet.defer import inlineCallbacks
+from twisted.internet.defer import inlineCallbacks, succeed
"""
Store test utility functions
@@ -40,7 +40,7 @@
def recordWithCalendarUserAddress(self, cuaddr):
- return self.recordsByCUA.get(cuaddr)
+ return succeed(self.recordsByCUA.get(cuaddr))
def addRecord(self, record):
@@ -117,7 +117,7 @@
def isProxyFor(self, other):
- return False
+ return succeed(False)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/datastore/util.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -100,6 +100,7 @@
+ at inlineCallbacks
def normalizationLookup(cuaddr, recordFunction, config):
"""
Lookup function to be passed to ical.normalizeCalendarUserAddresses.
@@ -108,23 +109,25 @@
record for the cuaddr.
"""
try:
- record = recordFunction(cuaddr)
+ record = yield recordFunction(cuaddr)
except Exception, e:
log.debug("Lookup of %s failed: %s" % (cuaddr, e))
record = None
if record is None:
- return (None, None, None)
+ returnValue((None, None, None))
else:
# RFC5545 syntax does not allow backslash escaping in
# parameter values. A double-quote is thus not allowed
# in a parameter value except as the start/end delimiters.
# Single quotes are allowed, so we convert any double-quotes
# to single-quotes.
- return (
- record.fullName.replace('"', "'"),
- record.uid,
- record.calendarUserAddresses,
+ returnValue(
+ (
+ record.displayName.replace('"', "'"),
+ record.uid,
+ record.calendarUserAddresses,
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/caldav/icalendardirectoryservice.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -37,8 +37,8 @@
"""
Return the record for the specified calendar user address.
- @return: the record.
- @rtype: L{ICalendarStoreDirectoryRecord}
+ @return: Deferred resulting in the record.
+ @rtype: L{Deferred} resulting in L{ICalendarStoreDirectoryRecord}
"""
Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/conduit.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -76,7 +76,8 @@
self.store = store
- def validRequst(self, source_guid, destination_guid):
+ @inlineCallbacks
+ def validRequest(self, source_guid, destination_guid):
"""
Verify that the specified GUIDs are valid for the request and return the
matching directory records.
@@ -86,22 +87,22 @@
@param destination_guid: GUID for the user to whom the request is being sent
@type destination_guid: C{str}
- @return: C{tuple} of L{IStoreDirectoryRecord}
+ @return: L{Deferred} resulting in C{tuple} of L{IStoreDirectoryRecord}
"""
- source = self.store.directoryService().recordWithUID(source_guid)
+ source = yield self.store.directoryService().recordWithUID(source_guid)
if source is None:
raise DirectoryRecordNotFoundError("Cross-pod source: {}".format(source_guid))
if not source.thisServer():
raise FailedCrossPodRequestError("Cross-pod source not on this server: {}".format(source_guid))
- destination = self.store.directoryService().recordWithUID(destination_guid)
+ destination = yield self.store.directoryService().recordWithUID(destination_guid)
if destination is None:
raise DirectoryRecordNotFoundError("Cross-pod destination: {}".format(destination_guid))
if destination.thisServer():
raise FailedCrossPodRequestError("Cross-pod destination on this server: {}".format(destination_guid))
- return (source, destination,)
+ returnValue((source, destination,))
@inlineCallbacks
@@ -186,7 +187,7 @@
@type supported_components: C{str}
"""
- _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
+ _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
action = {
"action": "shareinvite",
@@ -260,7 +261,7 @@
@type shareUID: C{str}
"""
- _ignore_sender, recipient = self.validRequst(ownerUID, shareeUID)
+ _ignore_sender, recipient = yield self.validRequest(ownerUID, shareeUID)
action = {
"action": "shareuninvite",
@@ -325,7 +326,7 @@
@type summary: C{str}
"""
- _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+ _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
action = {
"action": "sharereply",
@@ -398,7 +399,7 @@
actionName = "add-attachment"
shareeView = objectResource._parentCollection
- action, recipient = self._send(actionName, shareeView, objectResource)
+ action, recipient = yield self._send(actionName, shareeView, objectResource)
action["rids"] = rids
action["filename"] = filename
result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
@@ -458,7 +459,7 @@
actionName = "update-attachment"
shareeView = objectResource._parentCollection
- action, recipient = self._send(actionName, shareeView, objectResource)
+ action, recipient = yield self._send(actionName, shareeView, objectResource)
action["managedID"] = managed_id
action["filename"] = filename
result = yield self.sendRequest(shareeView._txn, recipient, action, stream, content_type)
@@ -514,7 +515,7 @@
actionName = "remove-attachment"
shareeView = objectResource._parentCollection
- action, recipient = self._send(actionName, shareeView, objectResource)
+ action, recipient = yield self._send(actionName, shareeView, objectResource)
action["rids"] = rids
action["managedID"] = managed_id
result = yield self.sendRequest(shareeView._txn, recipient, action)
@@ -557,6 +558,7 @@
# Sharer data access related apis
#
+ @inlineCallbacks
def _send(self, action, parent, child=None):
"""
Base behavior for an operation on a L{CommonHomeChild}.
@@ -570,7 +572,7 @@
ownerID = parent.external_id()
shareeUID = parent.viewerHome().uid()
- _ignore_sender, recipient = self.validRequst(shareeUID, ownerUID)
+ _ignore_sender, recipient = yield self.validRequest(shareeUID, ownerUID)
result = {
"action": action,
@@ -581,7 +583,7 @@
}
if child is not None:
result["resource_id"] = child.id()
- return result, recipient
+ returnValue((result, recipient))
@inlineCallbacks
@@ -644,7 +646,7 @@
@type kwargs: C{dict}
"""
- action, recipient = self._send(actionName, shareeView, objectResource)
+ action, recipient = yield self._send(actionName, shareeView, objectResource)
if args is not None:
action["arguments"] = args
if kwargs is not None:
@@ -710,7 +712,7 @@
servertoserver,
event_details,
):
- action, recipient = self._send("freebusy", calresource)
+ action, recipient = yield self._send("freebusy", calresource)
action["timerange"] = [timerange.start.getText(), timerange.end.getText()]
action["matchtotal"] = matchtotal
action["excludeuid"] = excludeuid
Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -179,11 +179,13 @@
davxml.Privilege(davxml.Read()),
)
- return davxml.ACL(
- # DAV:Read for all principals (includes anonymous)
- davxml.ACE(
- davxml.Principal(davxml.All()),
- davxml.Grant(*privs),
- davxml.Protected(),
- ),
+ return succeed(
+ davxml.ACL(
+ # DAV:Read for all principals (includes anonymous)
+ davxml.ACE(
+ davxml.Principal(davxml.All()),
+ davxml.Grant(*privs),
+ davxml.Protected(),
+ ),
+ )
)
Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/podding/test/test_conduit.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -123,29 +123,41 @@
}
- def test_validRequst(self):
+ @inlineCallbacks
+ def test_validRequest(self):
"""
Cross-pod request fails when there is no shared secret header present.
"""
conduit = PoddingConduit(self.storeUnderTest())
- r1, r2 = conduit.validRequst("user01", "puser02")
+ r1, r2 = yield conduit.validRequest("user01", "puser02")
self.assertTrue(r1 is not None)
self.assertTrue(r2 is not None)
- self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "bogus01", "user02")
- self.assertRaises(DirectoryRecordNotFoundError, conduit.validRequst, "user01", "bogus02")
- self.assertRaises(FailedCrossPodRequestError, conduit.validRequst, "user01", "user02")
+ self.assertFailure(
+ conduit.validRequest("bogus01", "user02"),
+ DirectoryRecordNotFoundError
+ )
+ self.assertFailure(
+ conduit.validRequest("user01", "bogus02"),
+ DirectoryRecordNotFoundError
+ )
+ self.assertFailure(
+ conduit.validRequest("user01", "user02"),
+ FailedCrossPodRequestError
+ )
+
+
class TestConduitToConduit(MultiStoreConduitTest):
class FakeConduit(PoddingConduit):
@inlineCallbacks
def send_fake(self, txn, ownerUID, shareeUID):
- _ignore_owner, sharee = self.validRequst(ownerUID, shareeUID)
+ _ignore_owner, sharee = yield self.validRequest(ownerUID, shareeUID)
action = {
"action": "fake",
"echo": "bravo"
Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/sql.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -922,102 +922,164 @@
@classproperty
def _addGroupQuery(cls):
gr = schema.GROUPS
- return Insert({gr.NAME: Parameter("name"),
- gr.GROUP_GUID: Parameter("groupGUID"),
- gr.MEMBERSHIP_HASH: Parameter("membershipHash")},
- Return=gr.GROUP_ID)
+ return Insert(
+ {
+ gr.NAME: Parameter("name"),
+ gr.GROUP_GUID: Parameter("groupUID"),
+ gr.MEMBERSHIP_HASH: Parameter("membershipHash")
+ },
+ Return=gr.GROUP_ID
+ )
@classproperty
def _updateGroupQuery(cls):
gr = schema.GROUPS
- return Update({gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
- gr.NAME: Parameter("name"), gr.MODIFIED: Parameter("timestamp")},
- Where=(gr.GROUP_GUID == Parameter("groupGUID")))
+ return Update(
+ {
+ gr.MEMBERSHIP_HASH: Parameter("membershipHash"),
+ gr.NAME: Parameter("name"),
+ gr.MODIFIED:
+ Parameter("timestamp")
+ },
+ Where=(gr.GROUP_GUID == Parameter("groupUID"))
+ )
@classproperty
- def _groupByGUID(cls):
+ def _groupByUID(cls):
gr = schema.GROUPS
- return Select([gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
- Where=(
- gr.GROUP_GUID == Parameter("groupGUID")
- )
- )
+ return Select(
+ [gr.GROUP_ID, gr.NAME, gr.MEMBERSHIP_HASH],
+ From=gr,
+ Where=(gr.GROUP_GUID == Parameter("groupUID"))
+ )
@classproperty
def _groupByID(cls):
gr = schema.GROUPS
- return Select([gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH], From=gr,
- Where=(
- gr.GROUP_ID == Parameter("groupID")
- )
- )
+ return Select(
+ [gr.GROUP_GUID, gr.NAME, gr.MEMBERSHIP_HASH],
+ From=gr,
+ Where=(gr.GROUP_ID == Parameter("groupID"))
+ )
@classproperty
def _deleteGroup(cls):
gr = schema.GROUPS
- return Delete(From=gr,
- Where=(gr.GROUP_ID == Parameter("groupID")))
+ return Delete(
+ From=gr,
+ Where=(gr.GROUP_ID == Parameter("groupID"))
+ )
- def addGroup(self, groupGUID, name, membershipHash):
+ def addGroup(self, groupUID, name, membershipHash):
"""
- @type groupGUID: C{UUID}
+ @type groupUID: C{unicode}
+ @type name: C{unicode}
+ @type membershipHash: C{str}
"""
- return self._addGroupQuery.on(self, name=name,
- groupGUID=str(groupGUID), membershipHash=membershipHash)
+ return self._addGroupQuery.on(
+ self,
+ name=name.encode("utf-8"),
+ groupUID=groupUID.encode("utf-8"),
+ membershipHash=membershipHash
+ )
- def updateGroup(self, groupGUID, name, membershipHash):
+ def updateGroup(self, groupUID, name, membershipHash):
"""
- @type groupGUID: C{UUID}
+ @type groupUID: C{unicode}
+ @type name: C{unicode}
+ @type membershipHash: C{str}
"""
timestamp = datetime.datetime.utcnow()
- return self._updateGroupQuery.on(self, name=name,
- groupGUID=str(groupGUID), timestamp=timestamp,
- membershipHash=membershipHash)
+ return self._updateGroupQuery.on(
+ self,
+ name=name.encode("utf-8"),
+ groupUID=groupUID.encode("utf-8"),
+ timestamp=timestamp,
+ membershipHash=membershipHash
+ )
@inlineCallbacks
- def groupByGUID(self, groupGUID):
+ def groupByUID(self, groupUID):
"""
- @type groupGUID: C{UUID}
+ Return or create a record for the group UID.
+
+ @type groupUID: C{unicode}
+
+ @return: Deferred firing with tuple of group ID C{str}, group name
+ C{unicode}, and membership hash C{str}
"""
- results = (yield self._groupByGUID.on(self, groupGUID=str(groupGUID)))
+ results = (
+ yield self._groupByUID.on(
+ self, groupUID=groupUID.encode("utf-8")
+ )
+ )
if results:
- returnValue(results[0])
+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # name
+ results[0][2], # membership hash
+ ))
else:
- savepoint = SavepointAction("groupByGUID")
+ savepoint = SavepointAction("groupByUID")
yield savepoint.acquire(self)
try:
- yield self.addGroup(groupGUID, "", "")
+ yield self.addGroup(groupUID, u"", "")
except Exception:
yield savepoint.rollback(self)
- results = (yield self._groupByGUID.on(self,
- groupGUID=str(groupGUID)))
+ results = (
+ yield self._groupByUID.on(
+ self, groupUID=groupUID.encode("utf-8")
+ )
+ )
if results:
- returnValue(results[0])
+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # name
+ results[0][2], # membership hash
+ ))
else:
raise
else:
yield savepoint.release(self)
- results = (yield self._groupByGUID.on(self,
- groupGUID=str(groupGUID)))
+ results = (
+ yield self._groupByUID.on(
+ self, groupUID=groupUID.encode("utf-8")
+ )
+ )
if results:
- returnValue(results[0])
+ returnValue((
+ results[0][0], # group id
+ results[0][1].decode("utf-8"), # name
+ results[0][2], # membership hash
+ ))
else:
raise
@inlineCallbacks
def groupByID(self, groupID):
+ """
+ Given a group ID, return the group UID, or raise NotFoundError
+
+ @type groupID: C{str}
+ @return: Deferred firing with a tuple of group UID C{unicode},
+ group name C{unicode}, and membership hash C{str}
+ """
try:
results = (yield self._groupByID.on(self, groupID=groupID))[0]
if results:
- results = [UUID("urn:uuid:" + results[0])] + results[1:]
+ results = (
+ results[0].decode("utf-8"),
+ results[1].decode("utf-8"),
+ results[2]
+ )
returnValue(results)
except IndexError:
raise NotFoundError
@@ -1037,7 +1099,7 @@
return Insert(
{
gm.GROUP_ID: Parameter("groupID"),
- gm.MEMBER_GUID: Parameter("memberGUID")
+ gm.MEMBER_GUID: Parameter("memberUID")
}
)
@@ -1050,7 +1112,7 @@
Where=(
gm.GROUP_ID == Parameter("groupID")
).And(
- gm.MEMBER_GUID == Parameter("memberGUID")
+ gm.MEMBER_GUID == Parameter("memberUID")
)
)
@@ -1069,25 +1131,35 @@
@classproperty
def _selectGroupsForQuery(cls):
+ gr = schema.GROUPS
gm = schema.GROUP_MEMBERSHIP
+
return Select(
- [gm.GROUP_ID],
- From=gm,
+ [gr.GROUP_GUID],
+ From=gr,
Where=(
- gm.MEMBER_GUID == Parameter("guid")
+ gr.GROUP_ID.In(
+ Select(
+ [gm.GROUP_ID],
+ From=gm,
+ Where=(
+ gm.MEMBER_GUID == Parameter("uid")
+ )
+ )
+ )
)
)
- def addMemberToGroup(self, memberGUID, groupID):
+ def addMemberToGroup(self, memberUID, groupID):
return self._addMemberToGroupQuery.on(
- self, groupID=groupID, memberGUID=str(memberGUID)
+ self, groupID=groupID, memberUID=memberUID.encode("utf-8")
)
- def removeMemberFromGroup(self, memberGUID, groupID):
+ def removeMemberFromGroup(self, memberUID, groupID):
return self._removeMemberFromGroupQuery.on(
- self, groupID=groupID, memberGUID=str(memberGUID)
+ self, groupID=groupID, memberUID=memberUID.encode("utf-8")
)
@@ -1107,25 +1179,29 @@
members = set()
results = (yield self._selectGroupMembersQuery.on(self, groupID=groupID))
for row in results:
- members.add(UUID("urn:uuid:" + row[0]))
+ members.add(row[0].decode("utf-8"))
returnValue(members)
@inlineCallbacks
- def groupsFor(self, guid):
+ def groupsFor(self, uid):
"""
- Returns the cached set of GUIDs for the groups this given guid is
+ Returns the cached set of UIDs for the groups this given uid is
a member of.
- @param guid: the guid
- @type guid: C{UUID}
+ @param uid: the uid
+ @type uid: C{unicode}
@return: the set of group IDs
@rtype: a Deferred which fires with a set() of C{int} group IDs
"""
groups = set()
- results = (yield self._selectGroupsForQuery.on(self, guid=str(guid)))
+ results = (
+ yield self._selectGroupsForQuery.on(
+ self, uid=uid.encode("utf-8")
+ )
+ )
for row in results:
- groups.add(row[0])
+ groups.add(row[0].decode("utf-8"))
returnValue(groups)
# End of Group Members
@@ -1199,13 +1275,23 @@
@classproperty
def _selectDelegateGroupsQuery(cls):
ds = schema.DELEGATE_GROUPS
+ gr = schema.GROUPS
+
return Select(
- [ds.GROUP_ID],
- From=ds,
+ [gr.GROUP_GUID],
+ From=gr,
Where=(
- ds.DELEGATOR == Parameter("delegator")
- ).And(
- ds.READ_WRITE == Parameter("readWrite")
+ gr.GROUP_ID.In(
+ Select(
+ [ds.GROUP_ID],
+ From=ds,
+ Where=(
+ ds.DELEGATOR == Parameter("delegator")
+ ).And(
+ ds.READ_WRITE == Parameter("readWrite")
+ )
+ )
+ )
)
)
@@ -1317,18 +1403,18 @@
Adds a row to the DELEGATES table. The delegate should not be a
group. To delegate to a group, call addDelegateGroup() instead.
- @param delegator: the GUID of the delegator
- @type delegator: C{UUID}
- @param delegate: the GUID of the delegate
- @type delegate: C{UUID}
+ @param delegator: the UID of the delegator
+ @type delegator: C{unicode}
+ @param delegate: the UID of the delegate
+ @type delegate: C{unicode}
@param readWrite: grant read and write access if True, otherwise
read-only access
@type readWrite: C{boolean}
"""
return self._addDelegateQuery.on(
self,
- delegator=str(delegator),
- delegate=str(delegate),
+ delegator=delegator.encode("utf-8"),
+ delegate=delegate.encode("utf-8"),
readWrite=1 if readWrite else 0
)
@@ -1339,8 +1425,8 @@
Adds a row to the DELEGATE_GROUPS table. The delegate should be a
group. To delegate to a person, call addDelegate() instead.
- @param delegator: the GUID of the delegator
- @type delegator: C{UUID}
+ @param delegator: the UID of the delegator
+ @type delegator: C{unicode}
@param delegateGroupID: the GROUP_ID of the delegate group
@type delegateGroupID: C{int}
@param readWrite: grant read and write access if True, otherwise
@@ -1349,7 +1435,7 @@
"""
return self._addDelegateGroupQuery.on(
self,
- delegator=str(delegator),
+ delegator=delegator.encode("utf-8"),
groupID=delegateGroupID,
readWrite=1 if readWrite else 0,
isExternal=1 if isExternal else 0
@@ -1361,18 +1447,18 @@
Removes a row from the DELEGATES table. The delegate should not be a
group. To remove a delegate group, call removeDelegateGroup() instead.
- @param delegator: the GUID of the delegator
- @type delegator: C{UUID}
- @param delegate: the GUID of the delegate
- @type delegate: C{UUID}
+ @param delegator: the UID of the delegator
+ @type delegator: C{unicode}
+ @param delegate: the UID of the delegate
+ @type delegate: C{unicode}
@param readWrite: remove read and write access if True, otherwise
read-only access
@type readWrite: C{boolean}
"""
return self._removeDelegateQuery.on(
self,
- delegator=str(delegator),
- delegate=str(delegate),
+ delegator=delegator.encode("utf-8"),
+ delegate=delegate.encode("utf-8"),
readWrite=1 if readWrite else 0
)
@@ -1382,8 +1468,8 @@
Removes a row from the DELEGATE_GROUPS table. The delegate should be a
group. To remove a delegate person, call removeDelegate() instead.
- @param delegator: the GUID of the delegator
- @type delegator: C{UUID}
+ @param delegator: the UID of the delegator
+ @type delegator: C{unicode}
@param delegateGroupID: the GROUP_ID of the delegate group
@type delegateGroupID: C{int}
@param readWrite: remove read and write access if True, otherwise
@@ -1392,26 +1478,26 @@
"""
return self._removeDelegateGroupQuery.on(
self,
- delegator=str(delegator),
+ delegator=delegator.encode("utf-8"),
groupID=delegateGroupID,
readWrite=1 if readWrite else 0
)
@inlineCallbacks
- def delegates(self, delegator, readWrite):
+ def delegates(self, delegator, readWrite, expanded=False):
"""
- Returns the GUIDs of all delegates for the given delegator. If
- delegate access was granted to any groups, those groups' members
- (flattened) will be included. No GUIDs of the groups themselves
- will be returned.
+ Returns the UIDs of all delegates for the given delegator. If
+ expanded is False, only the direct delegates (users and groups)
+ are returned. If expanded is True, the expanded membmership is
+ returned, not including the groups themselves.
- @param delegator: the GUID of the delegator
- @type delegator: C{UUID}
+ @param delegator: the UID of the delegator
+ @type delegator: C{unicode}
@param readWrite: the access-type to check for; read and write
access if True, otherwise read-only access
@type readWrite: C{boolean}
- @returns: the GUIDs of the delegates (for the specified access
+ @returns: the UIDs of the delegates (for the specified access
type)
@rtype: a Deferred resulting in a set
"""
@@ -1421,35 +1507,48 @@
results = (
yield self._selectDelegatesQuery.on(
self,
- delegator=str(delegator),
+ delegator=delegator.encode("utf-8"),
readWrite=1 if readWrite else 0
)
)
for row in results:
- delegates.add(UUID("urn:uuid:" + row[0]))
+ delegates.add(row[0].decode("utf-8"))
- # Finally get those who are in groups which have been delegated to
- results = (
- yield self._selectIndirectDelegatesQuery.on(
- self,
- delegator=str(delegator),
- readWrite=1 if readWrite else 0
+ if expanded:
+ # Get those who are in groups which have been delegated to
+ results = (
+ yield self._selectIndirectDelegatesQuery.on(
+ self,
+ delegator=delegator.encode("utf-8"),
+ readWrite=1 if readWrite else 0
+ )
)
- )
- for row in results:
- delegates.add(UUID("urn:uuid:" + row[0]))
+ for row in results:
+ delegates.add(row[0].decode("utf-8"))
+ else:
+ # Get the directly-delegated-to groups
+ results = (
+ yield self._selectDelegateGroupsQuery.on(
+ self,
+ delegator=delegator.encode("utf-8"),
+ readWrite=1 if readWrite else 0
+ )
+ )
+ for row in results:
+ delegates.add(row[0].decode("utf-8"))
+
returnValue(delegates)
@inlineCallbacks
def delegators(self, delegate, readWrite):
"""
- Returns the GUIDs of all delegators which have granted access to
+ Returns the UIDs of all delegators which have granted access to
the given delegate, either directly or indirectly via groups.
- @param delegate: the GUID of the delegate
- @type delegate: C{UUID}
+ @param delegate: the UID of the delegate
+ @type delegate: C{unicode}
@param readWrite: the access-type to check for; read and write
access if True, otherwise read-only access
@type readWrite: C{boolean}
@@ -1463,24 +1562,24 @@
results = (
yield self._selectDirectDelegatorsQuery.on(
self,
- delegate=str(delegate),
+ delegate=delegate.encode("utf-8"),
readWrite=1 if readWrite else 0
)
)
for row in results:
- delegators.add(UUID("urn:uuid:" + row[0]))
+ delegators.add(row[0].decode("utf-8"))
# Finally get those who have delegated to groups the delegate
# is a member of
results = (
yield self._selectIndirectDelegatorsQuery.on(
self,
- delegate=str(delegate),
+ delegate=delegate.encode("utf-8"),
readWrite=1 if readWrite else 0
)
)
for row in results:
- delegators.add(UUID("urn:uuid:" + row[0]))
+ delegators.add(row[0].decode("utf-8"))
returnValue(delegators)
@@ -1488,11 +1587,11 @@
@inlineCallbacks
def allGroupDelegates(self):
"""
- Return the GUIDs of all groups which have been delegated to. Useful
+ Return the UIDs of all groups which have been delegated to. Useful
for obtaining the set of groups which need to be synchronized from
the directory.
- @returns: the GUIDs of all delegated-to groups
+ @returns: the UIDs of all delegated-to groups
@rtype: a Deferred resulting in a set
"""
gr = schema.GROUPS
@@ -1505,7 +1604,7 @@
).on(self))
delegates = set()
for row in results:
- delegates.add(UUID("urn:uuid:" + row[0]))
+ delegates.add(row[0].decode("utf-8"))
returnValue(delegates)
@@ -1513,22 +1612,22 @@
@inlineCallbacks
def externalDelegates(self):
"""
- Returns a dictionary mapping delegate GUIDs to (read-group, write-group)
+ Returns a dictionary mapping delegate UIDs to (read-group, write-group)
tuples, including only those assignments that originated from the
directory.
- @returns: dictionary mapping delegator guid to (readDelegateGUID,
- writeDelegateGUID) tuples
+ @returns: dictionary mapping delegator uid to (readDelegateUID,
+ writeDelegateUID) tuples
@rtype: a Deferred resulting in a dictionary
"""
delegates = {}
# Get the externally managed delegates (which are all groups)
results = (yield self._selectExternalDelegateGroupsQuery.on(self))
- for delegator, readDelegateGUID, writeDelegateGUID in results:
- delegates[UUID(delegator)] = (
- UUID(readDelegateGUID) if readDelegateGUID else None,
- UUID(writeDelegateGUID) if writeDelegateGUID else None
+ for delegator, readDelegateUID, writeDelegateUID in results:
+ delegates[delegator.encode("utf-8")] = (
+ readDelegateUID.encode("utf-8") if readDelegateUID else None,
+ writeDelegateUID.encode("utf-8") if writeDelegateUID else None
)
returnValue(delegates)
@@ -1537,7 +1636,7 @@
@inlineCallbacks
def assignExternalDelegates(
self, delegator, readDelegateGroupID, writeDelegateGroupID,
- readDelegateGUID, writeDelegateGUID
+ readDelegateUID, writeDelegateUID
):
"""
Update the external delegate group table so we can quickly identify
@@ -1560,12 +1659,12 @@
)
# Store new assignments in the external comparison table
- if readDelegateGUID or writeDelegateGUID:
+ if readDelegateUID or writeDelegateUID:
readDelegateForDB = (
- str(readDelegateGUID) if readDelegateGUID else ""
+ readDelegateUID.encode("utf-8") if readDelegateUID else ""
)
writeDelegateForDB = (
- str(writeDelegateGUID) if writeDelegateGUID else ""
+ writeDelegateUID.encode("utf-8") if writeDelegateUID else ""
)
yield self._storeExternalDelegateGroupsPairQuery.on(
self,
@@ -2750,7 +2849,7 @@
returnValue(None)
# Determine if the user is local or external
- record = txn.directoryService().recordWithUID(uid)
+ record = yield txn.directoryService().recordWithUID(uid)
if record is None:
raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
@@ -6751,7 +6850,7 @@
created = False
elif create:
# Determine if the user is local or external
- record = txn.directoryService().recordWithUID(uid)
+ record = yield txn.directoryService().recordWithUID(uid)
if record is None:
raise DirectoryRecordNotFoundError("Cannot create home for UID since no directory record exists: {}".format(uid))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/common/datastore/test/util.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -46,7 +46,7 @@
from twisted.application.service import Service
from twisted.internet import reactor
from twisted.internet.defer import Deferred, inlineCallbacks
-from twisted.internet.defer import returnValue
+from twisted.internet.defer import returnValue, succeed
from twisted.internet.task import deferLater
from twisted.trial.unittest import TestCase
@@ -110,14 +110,14 @@
def recordWithUID(self, uid):
- return self.records.get(uid)
+ return succeed(self.records.get(uid))
def recordWithGUID(self, guid):
for record in self.records.itervalues():
if record.guid == guid:
- return record
- return None
+ return succeed(record)
+ return succeed(None)
def addRecord(self, record):
Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/client.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -15,35 +15,50 @@
##
import cPickle as pickle
+import uuid
from twext.python.log import Logger
from twext.who.directory import DirectoryRecord as BaseDirectoryRecord
from twext.who.directory import DirectoryService as BaseDirectoryService
-from twext.who.idirectory import RecordType
+from twext.who.idirectory import RecordType, IDirectoryService
import twext.who.idirectory
from twext.who.util import ConstantsContainer
+from twisted.cred.credentials import UsernamePassword
from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
from twisted.internet.protocol import ClientCreator
from twisted.protocols import amp
+from twisted.python.constants import Names, NamedConstant
+from txdav.caldav.datastore.scheduling.cuaddress import normalizeCUAddr
+from txdav.caldav.icalendardirectoryservice import ICalendarStoreDirectoryRecord
+from txdav.common.idirectoryservice import IStoreDirectoryService
from txdav.dps.commands import (
RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
+ MembersCommand, GroupsCommand,
VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand
)
+import txdav.who.delegates
import txdav.who.idirectory
+from txweb2.auth.digest import DigestedCredentials
from zope.interface import implementer
-
log = Logger()
-
##
## Client implementation of Directory Proxy Service
##
- at implementer(twext.who.idirectory.IDirectoryService)
+
+## MOVE2WHO TODOs:
+## augmented service
+## configuration of aggregate services
+## hooking up delegates
+## calverify needs deferreds, including:
+## component.normalizeCalendarUserAddresses
+
+ at implementer(IDirectoryService, IStoreDirectoryService)
class DirectoryService(BaseDirectoryService):
"""
Client side of directory proxy
@@ -51,17 +66,57 @@
recordType = ConstantsContainer(
(twext.who.idirectory.RecordType,
- txdav.who.idirectory.RecordType)
+ txdav.who.idirectory.RecordType,
+ txdav.who.delegates.RecordType)
)
+ fieldName = ConstantsContainer(
+ (twext.who.idirectory.FieldName,
+ txdav.who.idirectory.FieldName)
+ )
+ # def __init__(self, fieldNames, recordTypes):
+ # self.fieldName = fieldNames
+ # self.recordType = recordTypes
+
+ # MOVE2WHO
+ def getGroups(self, guids=None):
+ return succeed(set())
+
+ # Must maintain the hack for a bit longer:
+ def setPrincipalCollection(self, principalCollection):
+ """
+ Set the principal service that the directory relies on for doing proxy tests.
+
+ @param principalService: the principal service.
+ @type principalService: L{DirectoryProvisioningResource}
+ """
+ self.principalCollection = principalCollection
+
+ guid = "1332A615-4D3A-41FE-B636-FBE25BFB982E"
+
+ # END MOVE2WHO
+
+
+
+
def _dictToRecord(self, serializedFields):
"""
- This to be replaced by something awesome
+ Turn a dictionary of fields sent from the server into a directory
+ record
"""
if not serializedFields:
return None
+ # print("FIELDS", serializedFields)
+
+ # MOVE2WHO -- existing code assumes record.emailAddresses always exists,
+ # so adding this here, but perhaps we should change the behavior in
+ # twext.who itself:
+ # Add default empty list of email addresses
+ if self.fieldName.emailAddresses.name not in serializedFields:
+ serializedFields[self.fieldName.emailAddresses.name] = []
+
fields = {}
for fieldName, value in serializedFields.iteritems():
try:
@@ -70,8 +125,21 @@
# unknown field
pass
else:
- fields[field] = value
- fields[self.fieldName.recordType] = self.recordType.user
+ valueType = self.fieldName.valueType(field)
+ if valueType in (unicode, bool):
+ fields[field] = value
+ elif valueType is uuid.UUID:
+ fields[field] = uuid.UUID(value)
+ elif issubclass(valueType, Names):
+ if value is not None:
+ fields[field] = field.valueType.lookupByName(value)
+ else:
+ fields[field] = None
+ elif issubclass(valueType, NamedConstant):
+ if fieldName == "recordType": # Is there a better way?
+ fields[field] = self.recordType.lookupByName(value)
+
+ # print("AFTER:", fields)
return DirectoryRecord(self, fields)
@@ -127,10 +195,14 @@
def recordWithShortName(self, recordType, shortName):
+ # MOVE2WHO
+ # temporary hack until we can fix all callers not to pass strings:
+ if isinstance(recordType, (str, unicode)):
+ recordType = self.recordType.lookupByName(recordType)
return self._call(
RecordWithShortNameCommand,
self._processSingleRecord,
- recordType=recordType.description.encode("utf-8"),
+ recordType=recordType.name.encode("utf-8"),
shortName=shortName.encode("utf-8")
)
@@ -155,7 +227,7 @@
return self._call(
RecordsWithRecordTypeCommand,
self._processMultipleRecords,
- recordType=recordType.description.encode("utf-8")
+ recordType=recordType.name.encode("utf-8")
)
@@ -167,9 +239,83 @@
)
+ def listRecords(self, recordType):
+ # MOVE2WHO
+ return []
+
+ @inlineCallbacks
+ def recordWithCalendarUserAddress(self, address):
+ address = normalizeCUAddr(address)
+ record = None
+ if address.startswith("urn:uuid:"):
+ guid = address[9:]
+ record = yield self.recordWithGUID(guid)
+ elif address.startswith("mailto:"):
+ records = yield self.recordsWithEmailAddress(address[7:])
+ if records:
+ returnValue(records[0])
+ else:
+ returnValue(None)
+ elif address.startswith("/principals/"):
+ parts = address.split("/")
+ if len(parts) == 4:
+ if parts[2] == "__uids__":
+ guid = parts[3]
+ record = yield self.recordWithGUID(guid)
+ else:
+ recordType = self.fieldName.lookupByName(parts[2])
+ record = yield self.recordWithShortName(recordType, parts[3])
+
+ returnValue(record if record and record.hasCalendars else None)
+
+
+ @inlineCallbacks
+ def recordsMatchingTokens(self, tokens, context=None, limitResults=50,
+ timeoutSeconds=10):
+ rec = yield self.recordWithShortName(
+ twext.who.idirectory.RecordType.user,
+ u"wsanchez"
+ )
+ returnValue([rec])
+
+
+
+
+ at implementer(ICalendarStoreDirectoryRecord)
class DirectoryRecord(BaseDirectoryRecord):
+
+ @inlineCallbacks
+ def verifyCredentials(self, credentials):
+
+ # XYZZY REMOVE THIS, it bypasses all authentication!:
+ returnValue(True)
+
+ if isinstance(credentials, UsernamePassword):
+ log.debug("UsernamePassword")
+ returnValue(
+ (yield self.verifyPlaintextPassword(credentials.password))
+ )
+
+ elif isinstance(credentials, DigestedCredentials):
+ log.debug("DigestedCredentials")
+ returnValue(
+ (yield self.verifyHTTPDigest(
+ self.shortNames[0],
+ self.service.realmName,
+ credentials.fields["uri"],
+ credentials.fields["nonce"],
+ credentials.fields.get("cnonce", ""),
+ credentials.fields["algorithm"],
+ credentials.fields.get("nc", ""),
+ credentials.fields.get("qop", ""),
+ credentials.fields["response"],
+ credentials.method
+ ))
+ )
+
+
def verifyPlaintextPassword(self, password):
return self.service._call(
VerifyPlaintextPasswordCommand,
@@ -200,7 +346,165 @@
)
+ def members(self):
+ return self.service._call(
+ MembersCommand,
+ self.service._processMultipleRecords,
+ uid=self.uid.encode("utf-8")
+ )
+
+ def groups(self):
+ return self.service._call(
+ GroupsCommand,
+ self.service._processMultipleRecords,
+ uid=self.uid.encode("utf-8")
+ )
+
+
+ @property
+ def calendarUserAddresses(self):
+ if not self.hasCalendars:
+ return frozenset()
+
+ try:
+ cuas = set(
+ ["mailto:%s" % (emailAddress,)
+ for emailAddress in self.emailAddresses]
+ )
+ except AttributeError:
+ cuas = set()
+
+ try:
+ if self.guid:
+ if isinstance(self.guid, uuid.UUID):
+ guid = unicode(self.guid).upper()
+ else:
+ guid = self.guid
+ cuas.add("urn:uuid:{guid}".format(guid=guid))
+ except AttributeError:
+ # No guid
+ pass
+ cuas.add("/principals/__uids__/{uid}/".format(uid=self.uid))
+ for shortName in self.shortNames:
+ cuas.add("/principals/{rt}/{sn}/".format(
+ rt=self.recordType.name + "s", sn=shortName)
+ )
+ return frozenset(cuas)
+
+
+ def getCUType(self):
+ # Mapping from directory record.recordType to RFC2445 CUTYPE values
+ self._cuTypes = {
+ self.service.recordType.user: 'INDIVIDUAL',
+ self.service.recordType.group: 'GROUP',
+ self.service.recordType.resource: 'RESOURCE',
+ self.service.recordType.location: 'ROOM',
+ }
+
+ return self._cuTypes.get(self.recordType, "UNKNOWN")
+
+
+ @property
+ def displayName(self):
+ return self.fullNames[0]
+
+
+ def cacheToken(self):
+ """
+ Generate a token that can be uniquely used to identify the state of this record for use
+ in a cache.
+ """
+ return hash((
+ self.__class__.__name__,
+ self.service.realmName,
+ self.recordType.name,
+ self.shortNames,
+ self.guid,
+ self.hasCalendars,
+ ))
+
+
+ def canonicalCalendarUserAddress(self):
+ """
+ Return a CUA for this record, preferring in this order:
+ urn:uuid: form
+ mailto: form
+ first in calendarUserAddresses list
+ """
+
+ cua = ""
+ for candidate in self.calendarUserAddresses:
+ # Pick the first one, but urn:uuid: and mailto: can override
+ if not cua:
+ cua = candidate
+ # But always immediately choose the urn:uuid: form
+ if candidate.startswith("urn:uuid:"):
+ cua = candidate
+ break
+ # Prefer mailto: if no urn:uuid:
+ elif candidate.startswith("mailto:"):
+ cua = candidate
+ return cua
+
+
+ def enabledAsOrganizer(self):
+ # MOVE2WHO FIXME TO LOOK AT CONFIG
+ if self.recordType == self.service.recordType.user:
+ return True
+ elif self.recordType == DirectoryService.recordType_groups:
+ return False # config.Scheduling.Options.AllowGroupAsOrganizer
+ elif self.recordType == DirectoryService.recordType_locations:
+ return False # config.Scheduling.Options.AllowLocationAsOrganizer
+ elif self.recordType == DirectoryService.recordType_resources:
+ return False # config.Scheduling.Options.AllowResourceAsOrganizer
+ else:
+ return False
+
+
+ #MOVE2WHO
+ def thisServer(self):
+ return True
+
+
+ def isLoginEnabled(self):
+ return self.loginAllowed
+
+
+ #MOVE2WHO
+ def calendarsEnabled(self):
+ # In the old world, this *also* looked at config:
+ # return config.EnableCalDAV and self.enabledForCalendaring
+ return self.hasCalendars
+
+
+ def getAutoScheduleMode(self, organizer):
+ # MOVE2WHO Fix this to take organizer into account:
+ return self.autoScheduleMode
+
+
+ def canAutoSchedule(self, organizer=None):
+ # MOVE2WHO Fix this:
+ return True
+
+
+ # For scheduling/freebusy
+ # FIXME: doesn't this need to happen in the DPS?
+ @inlineCallbacks
+ def isProxyFor(self, other):
+ for recordType in (
+ txdav.who.delegates.RecordType.readDelegatorGroup,
+ txdav.who.delegates.RecordType.writeDelegatorGroup,
+ ):
+ delegatorGroup = yield self.service.recordWithShortName(
+ recordType, self.uid
+ )
+ if delegatorGroup:
+ if other in (yield delegatorGroup.members()):
+ returnValue(True)
+
+
+
# Test client:
Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/commands.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -89,6 +89,25 @@
+class MembersCommand(amp.Command):
+ arguments = [
+ ('uid', amp.String()),
+ ]
+ response = [
+ ('fieldsList', amp.String()),
+ ]
+
+
+class GroupsCommand(amp.Command):
+ arguments = [
+ ('uid', amp.String()),
+ ]
+ response = [
+ ('fieldsList', amp.String()),
+ ]
+
+
+
class VerifyPlaintextPasswordCommand(amp.Command):
arguments = [
('uid', amp.String()),
Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/server.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -18,28 +18,35 @@
import os
import uuid
+from calendarserver.tap.util import getDBPool, storeFromConfig
from twext.python.log import Logger
+from twext.who.aggregate import DirectoryService as AggregateDirectoryService
from twext.who.idirectory import RecordType
+from twext.who.ldap import DirectoryService as LDAPDirectoryService
from twisted.application import service
from twisted.application.strports import service as strPortsService
+from twisted.cred.credentials import UsernamePassword
from twisted.internet.defer import inlineCallbacks, returnValue
from twisted.internet.protocol import Factory
from twisted.plugin import IPlugin
from twisted.protocols import amp
+from twisted.python.constants import Names, NamedConstant
from twisted.python.filepath import FilePath
+from twisted.python.reflect import namedClass
from twisted.python.usage import Options, UsageError
from twistedcaldav.config import config
from twistedcaldav.stdconfig import DEFAULT_CONFIG, DEFAULT_CONFIG_FILE
from txdav.dps.commands import (
RecordWithShortNameCommand, RecordWithUIDCommand, RecordWithGUIDCommand,
RecordsWithRecordTypeCommand, RecordsWithEmailAddressCommand,
+ MembersCommand, GroupsCommand,
VerifyPlaintextPasswordCommand, VerifyHTTPDigestCommand,
# UpdateRecordsCommand, RemoveRecordsCommand
)
-from twext.who.ldap import DirectoryService as LDAPDirectoryService
+from txdav.who.augment import AugmentedDirectoryService
+from txdav.who.delegates import DirectoryService as DelegateDirectoryService
from txdav.who.xml import DirectoryService as XMLDirectoryService
from zope.interface import implementer
-from twisted.cred.credentials import UsernamePassword
log = Logger()
@@ -63,15 +70,21 @@
def recordToDict(self, record):
"""
- This to be replaced by something awesome
+ Turn a record in a dictionary of fields which can be reconstituted
+ within the client
"""
fields = {}
if record is not None:
for field, value in record.fields.iteritems():
- # print("%s: %s" % (field.name, value))
- valueType = self._directory.fieldName.valueType(field)
- if valueType is unicode:
+ valueType = record.service.fieldName.valueType(field)
+ # print("%s: %s (%s)" % (field.name, value, valueType))
+ if valueType in (unicode, bool):
fields[field.name] = value
+ elif valueType is uuid.UUID:
+ fields[field.name] = str(value)
+ elif issubclass(valueType, (Names, NamedConstant)):
+ fields[field.name] = value.name if value else None
+ # print("Server side fields", fields)
return fields
@@ -82,7 +95,7 @@
shortName = shortName.decode("utf-8")
log.debug("RecordWithShortName: {r} {n}", r=recordType, n=shortName)
record = (yield self._directory.recordWithShortName(
- RecordType.lookupByName(recordType), shortName)
+ self._directory.recordType.lookupByName(recordType), shortName)
)
fields = self.recordToDict(record)
response = {
@@ -158,6 +171,50 @@
returnValue(response)
+
+ @MembersCommand.responder
+ @inlineCallbacks
+ def members(self, uid):
+ uid = uid.decode("utf-8")
+ log.debug("Members: {u}", u=uid)
+ try:
+ record = (yield self._directory.recordWithUID(uid))
+ except Exception as e:
+ log.error("Failed in members", error=e)
+ record = None
+
+ fieldsList = []
+ if record is not None:
+ for member in (yield record.members()):
+ fieldsList.append(self.recordToDict(member))
+ response = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
+ @GroupsCommand.responder
+ @inlineCallbacks
+ def groups(self, uid):
+ uid = uid.decode("utf-8")
+ log.debug("Groups: {u}", u=uid)
+ try:
+ record = (yield self._directory.recordWithUID(uid))
+ except Exception as e:
+ log.error("Failed in groups", error=e)
+ record = None
+
+ fieldsList = []
+ for group in (yield record.groups()):
+ fieldsList.append(self.recordToDict(group))
+ response = {
+ "fieldsList": pickle.dumps(fieldsList),
+ }
+ log.debug("Responding with: {response}", response=response)
+ returnValue(response)
+
+
@VerifyPlaintextPasswordCommand.responder
@inlineCallbacks
def verifyPlaintextPassword(self, uid, password):
@@ -322,7 +379,7 @@
options = DirectoryProxyOptions
- def makeService(self, options):
+ def makeService(self, options, store=None):
"""
Return a service
"""
@@ -337,9 +394,14 @@
args = config.DirectoryProxy.Arguments
kwds = config.DirectoryProxy.Keywords
+ # FIXME: this needs to talk to its own separate database
+ if store is None:
+ pool, txnFactory = getDBPool(config)
+ store = storeFromConfig(config, txnFactory)
+
if directoryType == "OD":
from twext.who.opendirectory import DirectoryService as ODDirectoryService
- directory = ODDirectoryService(*args, **kwds)
+ primaryDirectory = ODDirectoryService(*args, **kwds)
elif directoryType == "LDAP":
authDN = kwds.pop("authDN", "")
@@ -350,14 +412,16 @@
creds = None
kwds["credentials"] = creds
debug = kwds.pop("debug", "")
- directory = LDAPDirectoryService(*args, _debug=debug, **kwds)
+ primaryDirectory = LDAPDirectoryService(
+ *args, _debug=debug, **kwds
+ )
elif directoryType == "XML":
path = kwds.pop("path", "")
if not path or not os.path.exists(path):
log.error("Path not found for XML directory: {p}", p=path)
fp = FilePath(path)
- directory = XMLDirectoryService(fp, *args, **kwds)
+ primaryDirectory = XMLDirectoryService(fp, *args, **kwds)
else:
log.error("Invalid DirectoryType: {dt}", dt=directoryType)
@@ -365,4 +429,43 @@
desc = "unix:{path}:mode=660".format(
path=config.DirectoryProxy.SocketPath
)
- return strPortsService(desc, DirectoryProxyAMPFactory(directory))
+ #
+ # Setup the Augment Service
+ #
+ if config.AugmentService.type:
+ augmentClass = namedClass(config.AugmentService.type)
+ log.info(
+ "Configuring augment service of type: {augmentClass}",
+ augmentClass=augmentClass
+ )
+ try:
+ augmentService = augmentClass(**config.AugmentService.params)
+ except IOError:
+ log.error("Could not start augment service")
+ raise
+ else:
+ augmentService = None
+
+ delegateDirectory = DelegateDirectoryService(
+ primaryDirectory.realmName,
+ store
+ )
+
+ aggregateDirectory = AggregateDirectoryService(
+ primaryDirectory.realmName,
+ (primaryDirectory, delegateDirectory)
+ )
+ try:
+ augmented = AugmentedDirectoryService(
+ aggregateDirectory, store, augmentService
+ )
+
+ # The delegate directory needs a way to look up user/group records
+ # so hand it a reference to the augmented directory.
+ # FIXME: is there a better pattern to use here?
+ delegateDirectory.setMasterDirectory(augmented)
+
+ except Exception as e:
+ log.error("Could not create directory service", error=e)
+ raise
+ return strPortsService(desc, DirectoryProxyAMPFactory(augmented))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/dps/test/test.xml 2014-03-05 18:24:32 UTC (rev 12819)
@@ -23,6 +23,7 @@
<record type="user">
<uid>__sagen__</uid>
+ <guid>B3B1158F-0564-4F5B-81E4-A89EA5FF81B0</guid>
<short-name>sagen</short-name>
<full-name>Morgen Sagen</full-name>
<password>negas</password>
Added: CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py (rev 0)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/augment.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -0,0 +1,255 @@
+# -*- test-case-name: txdav.who.test.test_augment -*-
+##
+# Copyright (c) 2013 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.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+"""
+Augmenting Directory Service
+"""
+
+from twext.who.idirectory import IDirectoryService, RecordType
+from twext.who.directory import DirectoryRecord
+from twext.who.directory import DirectoryService as BaseDirectoryService
+from twext.who.util import ConstantsContainer
+from txdav.who.idirectory import AutoScheduleMode, FieldName
+from txdav.who.idirectory import RecordType as CalRecordType
+from txdav.who.delegates import RecordType as DelegateRecordType
+from twisted.internet.defer import inlineCallbacks, returnValue
+from zope.interface import implementer
+
+from twext.python.log import Logger
+log = Logger()
+
+
+class AugmentedDirectoryRecord(DirectoryRecord):
+
+ def __init__(self, service, baseRecord, augmentedFields):
+ DirectoryRecord.__init__(self, service, augmentedFields)
+ self._baseRecord = baseRecord
+
+
+ @inlineCallbacks
+ def members(self):
+ augmented = []
+ records = yield self._baseRecord.members()
+ for record in records:
+ augmented.append((yield self.service.augment(record)))
+ returnValue(augmented)
+
+
+ @inlineCallbacks
+ def groups(self):
+ augmented = []
+ txn = self.service._store.newTransaction()
+ groupUIDs = yield txn.groupsFor(self.uid)
+ print("XYZZY GROU UIDS", groupUIDs)
+ for groupUID in groupUIDs:
+ groupRecord = yield self.service.recordWithShortName(
+ RecordType.group, groupUID
+ )
+ if groupRecord:
+ augmented.append((yield self.service.augment(groupRecord)))
+ returnValue(augmented)
+
+
+ at implementer(IDirectoryService)
+class AugmentedDirectoryService(BaseDirectoryService):
+
+ fieldName = ConstantsContainer((
+ BaseDirectoryService.fieldName,
+ FieldName,
+ ))
+
+ recordType = ConstantsContainer((
+ RecordType.user,
+ RecordType.group,
+ CalRecordType.location,
+ CalRecordType.resource,
+ CalRecordType.address,
+ DelegateRecordType.readDelegateGroup,
+ DelegateRecordType.writeDelegateGroup,
+ DelegateRecordType.readDelegatorGroup,
+ DelegateRecordType.writeDelegatorGroup,
+ ))
+
+
+ def __init__(self, directory, store, augmentDB):
+ BaseDirectoryService.__init__(self, directory.realmName)
+ self._directory = directory
+ self._store = store
+ self._augmentDB = augmentDB
+
+
+ def recordTypes(self):
+ return self._directory.recordTypes()
+
+
+ @inlineCallbacks
+ def recordsFromExpression(self, expression):
+ records = yield self._directory.recordsFromExpression(expression)
+ augmented = []
+ for record in records:
+ record = yield self.augment(record)
+ augmented.append(record)
+ returnValue(augmented)
+
+
+ @inlineCallbacks
+ def recordsWithFieldValue(self, fieldName, value):
+ records = yield self._directory.recordsWithFieldValue(
+ fieldName, value
+ )
+ augmented = []
+ for record in records:
+ record = yield self.augment(record)
+ augmented.append(record)
+ returnValue(augmented)
+
+
+ @inlineCallbacks
+ def recordWithUID(self, uid):
+ record = yield self._directory.recordWithUID(uid)
+ record = yield self.augment(record)
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordWithGUID(self, guid):
+ record = yield self._directory.recordWithGUID(guid)
+ record = yield self.augment(record)
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordsWithRecordType(self, recordType):
+ records = yield self._directory.recordsWithRecordType(recordType)
+ augmented = []
+ for record in records:
+ record = yield self.augment(record)
+ augmented.append(record)
+ returnValue(augmented)
+
+
+ @inlineCallbacks
+ def recordWithShortName(self, recordType, shortName):
+ record = yield self._directory.recordWithShortName(recordType, shortName)
+ record = yield self.augment(record)
+ returnValue(record)
+
+
+ @inlineCallbacks
+ def recordsWithEmailAddress(self, emailAddress):
+ records = yield self._directory.recordsWithEmailAddress(emailAddress)
+ augmented = []
+ for record in records:
+ record = yield self.augment(record)
+ augmented.append(record)
+ returnValue(augmented)
+
+
+ @inlineCallbacks
+ def updateRecords(self, records, create=False):
+ return self._directory.updateRecords(records, create=create)
+
+
+ @inlineCallbacks
+ def removeRecords(self, uids):
+ return self._directory.removeRecords(uids)
+
+
+ def assignToField(self, fields, name, value):
+ field = self.fieldName.lookupByName(name)
+ fields[field] = value
+
+
+ @inlineCallbacks
+ def augment(self, record):
+ if record is None:
+ returnValue(None)
+
+ # MOVE2WHO
+ # FIXME: hacked by appending an "s" -- need a mapping
+ try:
+ augmentRecord = yield self._augmentDB.getAugmentRecord(
+ record.uid,
+ record.recordType.name + "s"
+ )
+ except KeyError:
+ # Augments does not know about this record type, so return
+ # the original record
+ returnValue(record)
+
+ fields = record.fields.copy()
+
+ # print("Got augment record", augmentRecord)
+
+ if augmentRecord:
+ # record.enabled = augmentRecord.enabled
+ # record.serverID = augmentRecord.serverID
+ self.assignToField(
+ fields, "hasCalendars",
+ augmentRecord.enabledForCalendaring
+ )
+ self.assignToField(
+ fields, "hasContacts",
+ augmentRecord.enabledForAddressBooks
+ )
+ autoScheduleMode = {
+ "none": AutoScheduleMode.none,
+ "accept-always": AutoScheduleMode.accept,
+ "decline-always": AutoScheduleMode.decline,
+ "accept-if-free": AutoScheduleMode.acceptIfFree,
+ "decline-if-busy": AutoScheduleMode.declineIfBusy,
+ "automatic": AutoScheduleMode.acceptIfFreeDeclineIfBusy,
+ }.get(augmentRecord.autoScheduleMode, None)
+ self.assignToField(
+ fields, "autoScheduleMode",
+ autoScheduleMode
+ )
+ self.assignToField(
+ fields, "autoAcceptGroup",
+ unicode(augmentRecord.autoAcceptGroup)
+ )
+ self.assignToField(
+ fields, "loginAllowed",
+ augmentRecord.enabledForLogin
+ )
+
+ if (
+ (
+ fields.get(self.fieldName.lookupByName("hasCalendars"), False) or
+ fields.get(self.fieldName.lookupByName("hasContacts"), False)
+ ) and record.recordType == RecordType.group
+ ):
+ self.assignToField(fields, "hasCalendars", False)
+ self.assignToField(fields, "hasContacts", False)
+
+ # For augment records cloned from the Default augment record,
+ # don't emit this message:
+ if not augmentRecord.clonedFromDefault:
+ log.error("Group '%s(%s)' cannot be enabled for calendaring or address books" % (record.guid, record.shortNames[0],))
+
+ else:
+ # Groups are by default always enabled
+ # record.enabled = (record.recordType == record.service.recordType_groups)
+ # record.serverID = ""
+ self.assignToField(fields, "hasCalendars", False)
+ self.assignToField(fields, "hasContacts", False)
+ self.assignToField(fields, "loginAllowed", False)
+
+ # print("Augmented fields", fields)
+
+ # Clone to a new record with the augmented fields
+ returnValue(AugmentedDirectoryRecord(self, record, fields))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/delegates.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twext.who.test.test_delegates -*-
+# -*- test-case-name: txdav.who.test.test_delegates -*-
##
# Copyright (c) 2013 Apple Inc. All rights reserved.
#
@@ -19,13 +19,173 @@
Delegate assignments
"""
-from twisted.internet.defer import inlineCallbacks, returnValue
-from twext.who.idirectory import RecordType
+from twext.python.log import Logger
+from twext.who.directory import (
+ DirectoryService as BaseDirectoryService,
+ DirectoryRecord as BaseDirectoryRecord
+)
+from twext.who.expression import MatchExpression, MatchType
+from twext.who.idirectory import RecordType as BaseRecordType, FieldName
+from twisted.internet.defer import inlineCallbacks, returnValue, succeed
+from twisted.python.constants import Names, NamedConstant
-from twext.python.log import Logger
+
log = Logger()
+
+class RecordType(Names):
+ """
+ Constants for read-only delegates and read-write delegate groups
+ """
+
+ readDelegateGroup = NamedConstant()
+ readDelegateGroup.description = u"read-delegate-group"
+
+ writeDelegateGroup = NamedConstant()
+ writeDelegateGroup.description = u"write-delegate-group"
+
+ readDelegatorGroup = NamedConstant()
+ readDelegatorGroup.description = u"read-delegator-group"
+
+ writeDelegatorGroup = NamedConstant()
+ writeDelegatorGroup.description = u"write-delegator-group"
+
+
+class DirectoryRecord(BaseDirectoryRecord):
+
+
+ @inlineCallbacks
+ def members(self, expanded=False):
+ """
+ If this is a readDelegateGroup or writeDelegateGroup, the members
+ will consist of the records who are delegates *of* this record.
+ If this is a readDelegatorGroup or writeDelegatorGroup,
+ the members will consist of the records who have delegated *to*
+ this record.
+ """
+ # Here are some delegate assignments to test with:
+ # txn = self.service._store.newTransaction()
+ # yield txn.addDelegate(u"E415DBA7-40B5-49F5-A7CC-ACC81E4DEC79", u"494E462A-B16A-4A90-B77F-B9019DD73DAA", True)
+ # yield txn.addDelegate(u"__wsanchez__", u"__cdaboo__", True)
+ # yield txn.addDelegate(u"__wsanchez__", u"__dre__", False)
+ # groupID, name, membershipHash = (
+ # yield txn.groupByUID(u"494E462A-B16A-4A90-B77F-B9019DD73DAA")
+ # )
+ # print("XYZZY", groupID, name, membershipHash)
+ # yield txn.addDelegateGroup(u"E415DBA7-40B5-49F5-A7CC-ACC81E4DEC79", groupID, False)
+ # yield txn.commit()
+ ###
+
+ parentUID, proxyType = self.uid.split("#")
+ txn = self.service._store.newTransaction()
+
+ if self.recordType in (
+ RecordType.readDelegateGroup, RecordType.writeDelegateGroup
+ ): # Members are delegates of this record
+ readWrite = (self.recordType is RecordType.writeDelegateGroup)
+ delegateUIDs = (
+ yield txn.delegates(parentUID, readWrite, expanded=expanded)
+ )
+
+ else: # Members have delegated to this record
+ readWrite = (self.recordType is RecordType.writeDelegatorGroup)
+ delegateUIDs = (
+ yield txn.delegators(parentUID, readWrite)
+ )
+
+ records = []
+ for uid in delegateUIDs:
+ if uid != parentUID:
+ record = (yield self.service._masterDirectory.recordWithUID(uid))
+ if record is not None:
+ records.append(record)
+ yield txn.commit()
+
+ returnValue(records)
+
+
+def recordTypeToProxyType(recordType):
+ return {
+ RecordType.readDelegateGroup: "calendar-proxy-read",
+ RecordType.writeDelegateGroup: "calendar-proxy-write",
+ RecordType.readDelegatorGroup: "calendar-proxy-read-for",
+ RecordType.writeDelegatorGroup: "calendar-proxy-write-for",
+ }.get(recordType, None)
+
+
+def proxyTypeToRecordType(proxyType):
+ return {
+ "calendar-proxy-read": RecordType.readDelegateGroup,
+ "calendar-proxy-write": RecordType.writeDelegateGroup,
+ "calendar-proxy-read-for": RecordType.readDelegatorGroup,
+ "calendar-proxy-write-for": RecordType.writeDelegatorGroup,
+ }.get(proxyType, None)
+
+
+
+class DirectoryService(BaseDirectoryService):
+ """
+ Delegate directory service
+ """
+
+ recordType = RecordType
+
+
+ def __init__(self, realmName, store):
+ BaseDirectoryService.__init__(self, realmName)
+ self._store = store
+ self._masterDirectory = None
+
+
+ def setMasterDirectory(self, masterDirectory):
+ self._masterDirectory = masterDirectory
+
+
+ def recordWithShortName(self, recordType, shortName):
+ uid = shortName + "#" + recordTypeToProxyType(recordType)
+
+ record = DirectoryRecord(self, {
+ FieldName.uid: uid,
+ FieldName.recordType: recordType,
+ FieldName.shortNames: (uid,),
+ })
+ return succeed(record)
+
+
+ def recordWithUID(self, uid):
+ if "#" not in uid: # Not a delegate group uid
+ return succeed(None)
+ uid, proxyType = uid.split("#")
+ recordType = proxyTypeToRecordType(proxyType)
+ if recordType is None:
+ return succeed(None)
+ return self.recordWithShortName(recordType, uid)
+
+
+ @inlineCallbacks
+ def recordsFromExpression(self, expression, records=None):
+ """
+ It's only ever appropriate to look up delegate group record by
+ shortName or uid. When wrapped by an aggregate directory, looking up
+ by shortName will already go directly to recordWithShortName. However
+ when looking up by UID, it won't. Inspect the expression to see if
+ it's one we can handle.
+ """
+ if isinstance(expression, MatchExpression):
+ if(
+ (expression.fieldName is FieldName.uid) and
+ (expression.matchType is MatchType.equals) and
+ ("#" in expression.fieldValue)
+ ):
+ record = yield self.recordWithUID(expression.fieldValue)
+ if record is not None:
+ returnValue((record,))
+
+ returnValue(())
+
+
+
@inlineCallbacks
def addDelegate(txn, delegator, delegate, readWrite):
"""
@@ -39,12 +199,12 @@
@param readWrite: if True, read and write access is granted; read-only
access otherwise
"""
- if delegate.recordType == RecordType.group:
+ if delegate.recordType == BaseRecordType.group:
# find the groupID
- groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
- yield txn.addDelegateGroup(delegator.guid, groupID, readWrite)
+ groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+ yield txn.addDelegateGroup(delegator.uid, groupID, readWrite)
else:
- yield txn.addDelegate(delegator.guid, delegate.guid, readWrite)
+ yield txn.addDelegate(delegator.uid, delegate.uid, readWrite)
@inlineCallbacks
@@ -60,16 +220,16 @@
@param readWrite: if True, read and write access is revoked; read-only
access otherwise
"""
- if delegate.recordType == RecordType.group:
+ if delegate.recordType == BaseRecordType.group:
# find the groupID
- groupID, name, membershipHash = (yield txn.groupByGUID(delegate.guid))
- yield txn.removeDelegateGroup(delegator.guid, groupID, readWrite)
+ groupID, name, membershipHash = (yield txn.groupByUID(delegate.uid))
+ yield txn.removeDelegateGroup(delegator.uid, groupID, readWrite)
else:
- yield txn.removeDelegate(delegator.guid, delegate.guid, readWrite)
+ yield txn.removeDelegate(delegator.uid, delegate.uid, readWrite)
@inlineCallbacks
-def delegatesOf(txn, delegator, readWrite):
+def delegatesOf(txn, delegator, readWrite, expanded=False):
"""
Return the records of the delegates of "delegator". The type of access
is specified by the "readWrite" parameter.
@@ -83,10 +243,12 @@
"""
records = []
directory = delegator.service
- delegateGUIDs = (yield txn.delegates(delegator.guid, readWrite))
- for guid in delegateGUIDs:
- if guid != delegator.guid:
- record = (yield directory.recordWithGUID(guid))
+ delegateUIDs = (
+ yield txn.delegates(delegator.uid, readWrite, expanded=expanded)
+ )
+ for uid in delegateUIDs:
+ if uid != delegator.uid:
+ record = (yield directory.recordWithUID(uid))
if record is not None:
records.append(record)
returnValue(records)
@@ -107,10 +269,10 @@
"""
records = []
directory = delegate.service
- delegatorGUIDs = (yield txn.delegators(delegate.guid, readWrite))
- for guid in delegatorGUIDs:
- if guid != delegate.guid:
- record = (yield directory.recordWithGUID(guid))
+ delegatorUIDs = (yield txn.delegators(delegate.uid, readWrite))
+ for uid in delegatorUIDs:
+ if uid != delegate.uid:
+ record = (yield directory.recordWithUID(uid))
if record is not None:
records.append(record)
returnValue(records)
@@ -118,7 +280,7 @@
def allGroupDelegates(txn):
"""
- @return: the GUIDs of all groups which are currently delegated to
- @rtype: a Deferred which fires with a set() of GUID strings
+ @return: the UIDs of all groups which are currently delegated to
+ @rtype: a Deferred which fires with a set() of UIDs C{unicode}
"""
return txn.allGroupDelegates()
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/groups.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -1,4 +1,4 @@
-# -*- test-case-name: twext.who.test.test_groups -*-
+# -*- test-case-name: txdav.who.test.test_groups -*-
##
# Copyright (c) 2013 Apple Inc. All rights reserved.
#
@@ -68,7 +68,7 @@
# New implmementation
try:
- newGroupCacher.update(self.transaction)
+ yield newGroupCacher.update(self.transaction)
except Exception, e:
log.error(
"Failed to update new group membership cache ({error})",
@@ -76,13 +76,13 @@
)
# Old implmementation
- try:
- oldGroupCacher.updateCache()
- except Exception, e:
- log.error(
- "Failed to update old group membership cache ({error})",
- error=e
- )
+ # try:
+ # oldGroupCacher.updateCache()
+ # except Exception, e:
+ # log.error(
+ # "Failed to update old group membership cache ({error})",
+ # error=e
+ # )
else:
notBefore = (
@@ -126,25 +126,29 @@
class GroupRefreshWork(WorkItem, fromTable(schema.GROUP_REFRESH_WORK)):
- group = property(lambda self: self.groupGUID)
+ # Note, the schema has "groupGuid", but really it's a UID. At some point
+ # we should change the column name.
+ group = property(lambda self: self.groupGuid)
@inlineCallbacks
def doWork(self):
-
+ print("XYZZY IN GRW doWork", )
# Delete all other work items for this group
yield Delete(
- From=self.table, Where=(self.table.GROUP_GUID == self.groupGUID)
+ From=self.table, Where=(self.table.GROUP_GUID == self.groupGuid)
).on(self.transaction)
- groupCacher = getattr(self.transaction, "_groupCacher", None)
- if groupCacher is not None:
+ newGroupCacher = getattr(self.transaction, "_newGroupCacher", None)
+ if newGroupCacher is not None:
try:
- groupCacher.refreshGroup(self.transaction, self.groupGUID)
+ yield newGroupCacher.refreshGroup(
+ self.transaction, self.groupGuid
+ )
except Exception, e:
log.error(
"Failed to refresh group {group} {err}",
- group=self.groupGUID, err=e
+ group=self.groupGuid, err=e
)
else:
@@ -154,11 +158,11 @@
)
log.debug(
"Rescheduling group refresh for {group}: {when}",
- group=self.groupGUID, when=notBefore
+ group=self.groupGuid, when=notBefore
)
yield self.transaction.enqueue(
GroupRefreshWork,
- groupGUID=self.groupGUID, notBefore=notBefore
+ groupGuid=self.groupGuid, notBefore=notBefore
)
@@ -182,6 +186,7 @@
)
).on(self.transaction)
+ # MOVE2WHO
# TODO: Pull this over from groupcacher branch
@@ -201,6 +206,7 @@
records.add(record)
for member in (yield record.members()):
if member not in records:
+ #MOVE2WHO
#TODO: HACK for old-style XML. FIX
if (
member.recordType != RecordType.group and
@@ -219,14 +225,14 @@
of two lists -- one for added/updated assignments, and one for removed
assignments.
- @param old: dictionary of delegator: (readGroupGUID, writeGroupGUID)
+ @param old: dictionary of delegator: (readGroupUID, writeGroupUID)
@type old: C{dict}
- @param new: dictionary of delegator: (readGroupGUID, writeGroupGUID)
+ @param new: dictionary of delegator: (readGroupUID, writeGroupUID)
@type new: C{dict}
@return: Tuple of two lists; the first list contains tuples of (delegator,
- (readGroupGUID, writeGroupGUID)), and represents all the new or updated
+ (readGroupUID, writeGroupUID)), and represents all the new or updated
assignments. The second list contains all the delegators which used to
have a delegate but don't anymore.
"""
@@ -269,19 +275,21 @@
# yield self.applyExternalAssignments(txn, externalAssignments)
# Figure out which groups matter
- groupGUIDs = yield self.groupsToRefresh(txn)
+ groupUIDs = yield self.groupsToRefresh(txn)
self.log.debug(
- "Number of groups to refresh: {num}", num=len(groupGUIDs)
+ "Number of groups to refresh: {num}", num=len(groupUIDs)
)
# For each of those groups, create a per-group refresh work item
- for groupGUID in groupGUIDs:
+ for groupUID in groupUIDs:
notBefore = (
datetime.datetime.utcnow() +
datetime.timedelta(seconds=1)
)
+ self.log.debug("Enqueuing group refresh for {u}", u=groupUID)
yield txn.enqueue(
- GroupRefreshWork, groupGUID=groupGUID, notBefore=notBefore
+ GroupRefreshWork, groupGuid=groupUID, notBefore=notBefore
)
+ self.log.debug("Enqueued group refresh for {u}", u=groupUID)
@inlineCallbacks
@@ -290,84 +298,91 @@
oldAssignments = (yield txn.externalDelegates())
# external assignments is of the form:
- # { delegatorGUID: (readDelegateGroupGUID, writeDelegateGroupGUID),
+ # { delegatorUID: (readDelegateGroupUID, writeDelegateGroupUID),
# }
changed, removed = diffAssignments(oldAssignments, newAssignments)
if changed:
for (
- delegatorGUID, (readDelegateGUID, writeDelegateGUID)
+ delegatorUID, (readDelegateUID, writeDelegateUID)
) in changed:
readDelegateGroupID = writeDelegateGroupID = None
- if readDelegateGUID:
+ if readDelegateUID:
readDelegateGroupID, _ignore_name, hash = (
- yield txn.groupByGUID(readDelegateGUID)
+ yield txn.groupByUID(readDelegateUID)
)
- if writeDelegateGUID:
+ if writeDelegateUID:
writeDelegateGroupID, _ignore_name, hash = (
- yield txn.groupByGUID(writeDelegateGUID)
+ yield txn.groupByUID(writeDelegateUID)
)
yield txn.assignExternalDelegates(
- delegatorGUID, readDelegateGroupID, writeDelegateGroupID,
- readDelegateGUID, writeDelegateGUID
+ delegatorUID, readDelegateGroupID, writeDelegateGroupID,
+ readDelegateUID, writeDelegateUID
)
if removed:
- for delegatorGUID in removed:
+ for delegatorUID in removed:
yield txn.assignExternalDelegates(
- delegatorGUID, None, None, None, None
+ delegatorUID, None, None, None, None
)
@inlineCallbacks
- def refreshGroup(self, txn, groupGUID):
+ def refreshGroup(self, txn, groupUID):
# Does the work of a per-group refresh work item
- # Faults in the flattened membership of a group, as GUIDs
+ # Faults in the flattened membership of a group, as UIDs
# and updates the GROUP_MEMBERSHIP table
- record = (yield self.directory.recordWithGUID(groupGUID))
- membershipHashContent = hashlib.md5()
- members = (yield expandedMembers(record))
- members = list(members)
- members.sort(cmp=lambda x, y: cmp(x.guid, y.guid))
- for member in members:
- membershipHashContent.update(str(member.guid))
- membershipHash = membershipHashContent.hexdigest()
- groupID, _ignore_cachedName, cachedMembershipHash = (
- yield txn.groupByGUID(groupGUID)
- )
-
- if cachedMembershipHash != membershipHash:
- membershipChanged = True
- self.log.debug(
- "Group '{group}' changed", group=record.fullNames[0]
+ self.log.debug("Faulting in group: {g}", g=groupUID)
+ record = (yield self.directory.recordWithUID(groupUID))
+ if record is None:
+ # FIXME: the group has disappeared from the directory.
+ # How do we want to handle this?
+ self.log.info("Group has disappeared: {g}", g=groupUID)
+ else:
+ self.log.debug("Got group record: {u}", u=record.uid)
+ membershipHashContent = hashlib.md5()
+ members = (yield expandedMembers(record))
+ members = list(members)
+ members.sort(cmp=lambda x, y: cmp(x.uid, y.uid))
+ for member in members:
+ membershipHashContent.update(str(member.uid))
+ membershipHash = membershipHashContent.hexdigest()
+ groupID, _ignore_cachedName, cachedMembershipHash = (
+ yield txn.groupByUID(groupUID)
)
- else:
- membershipChanged = False
- yield txn.updateGroup(groupGUID, record.fullNames[0], membershipHash)
+ if cachedMembershipHash != membershipHash:
+ membershipChanged = True
+ self.log.debug(
+ "Group '{group}' changed", group=record.fullNames[0]
+ )
+ else:
+ membershipChanged = False
- if membershipChanged:
- newMemberGUIDs = set()
- for member in members:
- newMemberGUIDs.add(member.guid)
- yield self.synchronizeMembers(txn, groupID, newMemberGUIDs)
+ yield txn.updateGroup(groupUID, record.fullNames[0], membershipHash)
- yield self.scheduleEventReconciliations(txn, groupID, groupGUID)
+ if membershipChanged:
+ newMemberUIDs = set()
+ for member in members:
+ newMemberUIDs.add(member.uid)
+ yield self.synchronizeMembers(txn, groupID, newMemberUIDs)
+ yield self.scheduleEventReconciliations(txn, groupID, groupUID)
+
@inlineCallbacks
- def synchronizeMembers(self, txn, groupID, newMemberGUIDs):
+ def synchronizeMembers(self, txn, groupID, newMemberUIDs):
numRemoved = numAdded = 0
- cachedMemberGUIDs = (yield txn.membersOfGroup(groupID))
+ cachedMemberUIDs = (yield txn.membersOfGroup(groupID))
- for memberGUID in cachedMemberGUIDs:
- if memberGUID not in newMemberGUIDs:
+ for memberUID in cachedMemberUIDs:
+ if memberUID not in newMemberUIDs:
numRemoved += 1
- yield txn.removeMemberFromGroup(memberGUID, groupID)
+ yield txn.removeMemberFromGroup(memberUID, groupID)
- for memberGUID in newMemberGUIDs:
- if memberGUID not in cachedMemberGUIDs:
+ for memberUID in newMemberUIDs:
+ if memberUID not in cachedMemberUIDs:
numAdded += 1
- yield txn.addMemberToGroup(memberGUID, groupID)
+ yield txn.addMemberToGroup(memberUID, groupID)
returnValue((numAdded, numRemoved))
@@ -378,23 +393,23 @@
The members of the given group as recorded in the db
"""
members = set()
- memberGUIDs = (yield txn.membersOfGroup(groupID))
- for guid in memberGUIDs:
- record = (yield self.directory.recordWithGUID(guid))
+ memberUIDs = (yield txn.membersOfGroup(groupID))
+ for uid in memberUIDs:
+ record = (yield self.directory.recordWithUID(uid))
if record is not None:
members.add(record)
returnValue(members)
- def cachedGroupsFor(self, txn, guid):
+ def cachedGroupsFor(self, txn, uid):
"""
- The IDs of the groups the guid is a member of
+ The UIDs of the groups the uid is a member of
"""
- return txn.groupsFor(guid)
+ return txn.groupsFor(uid)
@inlineCallbacks
- def scheduleEventReconciliations(self, txn, groupID, groupGUID):
+ def scheduleEventReconciliations(self, txn, groupID, groupUID):
"""
Find all events who have this groupID as an attendee and create
work items for them.
@@ -415,29 +430,29 @@
)
log.debug(
"scheduling group reconciliation for "
- "({eventID}, {groupID}, {groupGUID}): {when}",
+ "({eventID}, {groupID}, {groupUID}): {when}",
eventID=eventID,
groupID=groupID,
- groupGUID=groupGUID,
+ groupUID=groupUID,
when=notBefore)
yield txn.enqueue(
GroupAttendeeReconciliationWork,
eventID=eventID,
groupID=groupID,
- groupGUID=groupGUID,
+ groupGuid=groupUID,
notBefore=notBefore
)
@inlineCallbacks
def groupsToRefresh(self, txn):
- delegatedGUIDs = set((yield allGroupDelegates(txn)))
+ delegatedUIDs = set((yield allGroupDelegates(txn)))
self.log.info(
- "There are {count} group delegates", count=len(delegatedGUIDs)
+ "There are {count} group delegates", count=len(delegatedUIDs)
)
- attendeeGroupGUIDs = set()
+ attendeeGroupUIDs = set()
# get all groups from events
groupAttendee = schema.GROUP_ATTENDEE
@@ -447,7 +462,7 @@
).on(txn)
groupIDs = set([row[0] for row in rows])
- # get groupGUIDs
+ # get groupUIDs
if groupIDs:
gr = schema.GROUPS
rows = yield Select(
@@ -455,6 +470,6 @@
From=gr,
Where=gr.GROUP_ID.In(groupIDs)
).on(txn)
- attendeeGroupGUIDs = set([row[0] for row in rows])
+ attendeeGroupUIDs = set([row[0] for row in rows])
- returnValue(delegatedGUIDs.union(attendeeGroupGUIDs))
+ returnValue(delegatedUIDs.union(attendeeGroupUIDs))
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/accounts/accounts.xml 2014-03-05 18:24:32 UTC (rev 12819)
@@ -43,6 +43,7 @@
<name>Wilfredo Sanchez Vega</name>
<first-name>Wilfredo</first-name>
<last-name>Sanchez Vega</last-name>
+ <auto-schedule-mode><accept-if-free-decline-if-busy /></auto-schedule-mode>
</user>
<user>
<uid>cdaboo</uid>
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_delegates.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -92,15 +92,21 @@
# Add group delegate, but before the group membership has been
# pulled in
yield addDelegate(txn, delegator, group1, True)
- delegates = (yield delegatesOf(txn, delegator, True))
+ # Passing expanded=False will return the group
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=False))
+ self.assertEquals(1, len(delegates))
+ self.assertEquals(delegates[0].uid, u"__top_group_1__")
+ # Passing expanded=True will return not the group -- it only returns
+ # non-groups
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(0, len(delegates))
# Now refresh the group and there will be 3 delegates (contained
# within 2 nested groups)
# guid = "49b350c69611477b94d95516b13856ab"
- yield self.groupCacher.refreshGroup(txn, group1.guid)
- yield self.groupCacher.refreshGroup(txn, group2.guid)
- delegates = (yield delegatesOf(txn, delegator, True))
+ yield self.groupCacher.refreshGroup(txn, group1.uid)
+ yield self.groupCacher.refreshGroup(txn, group2.uid)
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(
set(["sagen", "cdaboo", "glyph"]),
set([d.shortNames[0] for d in delegates])
@@ -112,14 +118,12 @@
yield addDelegate(txn, delegator, group2, True)
groups = (yield allGroupDelegates(txn))
self.assertEquals(
- set([
- UUID("49b350c69611477b94d95516b13856ab"),
- UUID("86144f73345a409782f1b782672087c7")
- ]), set(groups))
+ set([u'__sub_group_1__', u'__top_group_1__']), set(groups)
+ )
# Delegate to a user who is already indirectly delegated-to
yield addDelegate(txn, delegator, delegate1, True)
- delegates = (yield delegatesOf(txn, delegator, True))
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(
set(["sagen", "cdaboo", "glyph"]),
set([d.shortNames[0] for d in delegates])
@@ -131,12 +135,12 @@
record = (
yield self.xmlService.recordWithShortName(RecordType.user, name)
)
- newSet.add(record.guid)
- groupID, name, membershipHash = (yield txn.groupByGUID(group1.guid))
+ newSet.add(record.uid)
+ groupID, name, membershipHash = (yield txn.groupByUID(group1.uid))
numAdded, numRemoved = (
yield self.groupCacher.synchronizeMembers(txn, groupID, newSet)
)
- delegates = (yield delegatesOf(txn, delegator, True))
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(
set(["sagen", "cdaboo", "glyph", "dre"]),
set([d.shortNames[0] for d in delegates])
@@ -144,7 +148,7 @@
# Remove delegate access from the top group
yield removeDelegate(txn, delegator, group1, True)
- delegates = (yield delegatesOf(txn, delegator, True))
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(
set(["sagen", "cdaboo"]),
set([d.shortNames[0] for d in delegates])
@@ -152,7 +156,7 @@
# Remove delegate access from the sub group
yield removeDelegate(txn, delegator, group2, True)
- delegates = (yield delegatesOf(txn, delegator, True))
+ delegates = (yield delegatesOf(txn, delegator, True, expanded=True))
self.assertEquals(
set(["sagen"]),
set([d.shortNames[0] for d in delegates])
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/test/test_groups.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -24,7 +24,6 @@
from twisted.internet.defer import inlineCallbacks
from twistedcaldav.test.util import StoreTestCase
from txdav.common.icommondatastore import NotFoundError
-from uuid import UUID
class GroupCacherTest(StoreTestCase):
@@ -69,22 +68,20 @@
txn = store.newTransaction()
record = yield self.xmlService.recordWithUID(u"__top_group_1__")
- yield self.groupCacher.refreshGroup(txn, record.guid)
+ yield self.groupCacher.refreshGroup(txn, record.uid)
- groupID, name, membershipHash = (yield txn.groupByGUID(record.guid))
- self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
+ groupID, name, membershipHash = (yield txn.groupByUID(record.uid))
- groupGUID, name, membershipHash = (yield txn.groupByID(groupID))
- self.assertEquals(groupGUID, record.guid)
- self.assertEquals(name, "Top Group 1")
- self.assertEquals(membershipHash, "4b0e162f2937f0f3daa6d10e5a6a6c33")
+ self.assertEquals(membershipHash, "f380860ff5e02c2433fbd4b5ed3e090c")
+ groupUID, name, membershipHash = (yield txn.groupByID(groupID))
+ self.assertEquals(groupUID, record.uid)
+ self.assertEquals(name, u"Top Group 1")
+ self.assertEquals(membershipHash, "f380860ff5e02c2433fbd4b5ed3e090c")
+
members = (yield txn.membersOfGroup(groupID))
self.assertEquals(
- set([UUID("9064df911dbc4e079c2b6839b0953876"),
- UUID("4ad155cbae9b475f986ce08a7537893e"),
- UUID("3bdcb95484d54f6d8035eac19a6d6e1f"),
- UUID("7d45cb10479e456bb54d528958c5734b")]),
+ set([u'__cdaboo__', u'__glyph__', u'__sagen__', u'__wsanchez__']),
members
)
@@ -97,8 +94,8 @@
# sagen is in the top group, even though it's actually one level
# removed
record = yield self.xmlService.recordWithUID(u"__sagen__")
- groups = (yield self.groupCacher.cachedGroupsFor(txn, record.guid))
- self.assertEquals(set([groupID]), groups)
+ groups = (yield self.groupCacher.cachedGroupsFor(txn, record.uid))
+ self.assertEquals(set([u"__top_group_1__"]), groups)
@inlineCallbacks
@@ -113,9 +110,9 @@
txn = store.newTransaction()
# Refresh the group so it's assigned a group_id
- guid = UUID("49b350c69611477b94d95516b13856ab")
- yield self.groupCacher.refreshGroup(txn, guid)
- groupID, name, membershipHash = (yield txn.groupByGUID(guid))
+ uid = u"__top_group_1__"
+ yield self.groupCacher.refreshGroup(txn, uid)
+ groupID, name, membershipHash = (yield txn.groupByUID(uid))
# Remove two members, and add one member
newSet = set()
@@ -126,7 +123,7 @@
name
)
)
- newSet.add(record.guid)
+ newSet.add(record.uid)
numAdded, numRemoved = (
yield self.groupCacher.synchronizeMembers(
txn, groupID, newSet
@@ -159,12 +156,12 @@
# Non-existent groupID
self.failUnlessFailure(txn.groupByID(42), NotFoundError)
- guid = UUID("49b350c69611477b94d95516b13856ab")
- hash = "4b0e162f2937f0f3daa6d10e5a6a6c33"
- yield self.groupCacher.refreshGroup(txn, guid)
- groupID, name, membershipHash = (yield txn.groupByGUID(guid))
+ uid = u"__top_group_1__"
+ hash = "f380860ff5e02c2433fbd4b5ed3e090c"
+ yield self.groupCacher.refreshGroup(txn, uid)
+ groupID, name, membershipHash = (yield txn.groupByUID(uid))
results = (yield txn.groupByID(groupID))
- self.assertEquals([guid, "Top Group 1", hash], results)
+ self.assertEquals((uid, u"Top Group 1", hash), results)
@inlineCallbacks
@@ -177,32 +174,31 @@
self.assertEquals(oldExternalAssignments, {})
newAssignments = {
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
- (None, UUID("49B350C6-9611-477B-94D9-5516B13856AB"))
+ u"__wsanchez__": (None, u"__top_group_1__")
}
yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
oldExternalAssignments = (yield txn.externalDelegates())
self.assertEquals(
oldExternalAssignments,
{
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+ u"__wsanchez__":
(
None,
- UUID("49B350C6-9611-477B-94D9-5516B13856AB")
+ u"__top_group_1__"
)
}
)
newAssignments = {
- UUID("7D45CB10-479E-456B-B54D-528958C5734B"):
+ u"__cdaboo__":
(
- UUID("86144F73-345A-4097-82F1-B782672087C7"),
+ u"__sub_group_1__",
None
),
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+ u"__wsanchez__":
(
- UUID("86144F73-345A-4097-82F1-B782672087C7"),
- UUID("49B350C6-9611-477B-94D9-5516B13856AB")
+ u"__sub_group_1__",
+ u"__top_group_1__"
),
}
yield self.groupCacher.applyExternalAssignments(txn, newAssignments)
@@ -210,14 +206,14 @@
self.assertEquals(
oldExternalAssignments,
{
- UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
+ u"__wsanchez__":
(
- UUID('86144f73-345a-4097-82f1-b782672087c7'),
- UUID('49b350c6-9611-477b-94d9-5516b13856ab')
+ u"__sub_group_1__",
+ u"__top_group_1__"
),
- UUID('7d45cb10-479e-456b-b54d-528958c5734b'):
+ u"__cdaboo__":
(
- UUID('86144f73-345a-4097-82f1-b782672087c7'),
+ u"__sub_group_1__",
None
)
}
@@ -228,44 +224,44 @@
allGroupDelegates,
set(
[
- UUID('49b350c6-9611-477b-94d9-5516b13856ab'),
- UUID('86144f73-345a-4097-82f1-b782672087c7')
+ u"__top_group_1__",
+ u"__sub_group_1__"
]
)
)
# Fault in the read-only group
- yield self.groupCacher.refreshGroup(txn, UUID('86144f73-345a-4097-82f1-b782672087c7'))
+ yield self.groupCacher.refreshGroup(txn, u"__sub_group_1__")
# Wilfredo should have Sagen and Daboo as read-only delegates
delegates = (yield txn.delegates(
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
+ u"__wsanchez__", False, expanded=True)
)
self.assertEquals(
delegates,
set(
[
- UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
- UUID('7d45cb10-479e-456b-b54d-528958c5734b')
+ u"__sagen__",
+ u"__cdaboo__"
]
)
)
# Fault in the read-write group
- yield self.groupCacher.refreshGroup(txn, UUID('49b350c6-9611-477b-94d9-5516b13856ab'))
+ yield self.groupCacher.refreshGroup(txn, u"__top_group_1__")
# Wilfredo should have 4 users as read-write delegates
delegates = (yield txn.delegates(
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
+ u"__wsanchez__", True, expanded=True)
)
self.assertEquals(
delegates,
set(
[
- UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'),
- UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
- UUID('7d45cb10-479e-456b-b54d-528958c5734b'),
- UUID('9064df91-1dbc-4e07-9c2b-6839b0953876')
+ u"__wsanchez__",
+ u"__sagen__",
+ u"__cdaboo__",
+ u"__glyph__"
]
)
)
@@ -275,9 +271,9 @@
# Now, remove some external assignments
#
newAssignments = {
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"):
+ u"__wsanchez__":
(
- UUID("86144F73-345A-4097-82F1-B782672087C7"),
+ u"__sub_group_1__",
None
),
}
@@ -286,9 +282,9 @@
self.assertEquals(
oldExternalAssignments,
{
- UUID('3bdcb954-84d5-4f6d-8035-eac19a6d6e1f'):
+ u"__wsanchez__":
(
- UUID('86144f73-345a-4097-82f1-b782672087c7'),
+ u"__sub_group_1__",
None
),
}
@@ -299,28 +295,28 @@
allGroupDelegates,
set(
[
- UUID('86144f73-345a-4097-82f1-b782672087c7')
+ u"__sub_group_1__"
]
)
)
# Wilfredo should have Sagen and Daboo as read-only delegates
delegates = (yield txn.delegates(
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), False)
+ u"__wsanchez__", False, expanded=True)
)
self.assertEquals(
delegates,
set(
[
- UUID('4ad155cb-ae9b-475f-986c-e08a7537893e'),
- UUID('7d45cb10-479e-456b-b54d-528958c5734b')
+ u"__sagen__",
+ u"__cdaboo__"
]
)
)
# Wilfredo should have no read-write delegates
delegates = (yield txn.delegates(
- UUID("3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F"), True)
+ u"__wsanchez__", True, expanded=True)
)
self.assertEquals(
delegates,
@@ -333,7 +329,7 @@
allGroupDelegates,
set(
[
- UUID('86144f73-345a-4097-82f1-b782672087c7')
+ u"__sub_group_1__"
]
)
)
@@ -424,7 +420,6 @@
<record type="user">
<uid>__wsanchez__</uid>
- <guid>3BDCB954-84D5-4F6D-8035-EAC19A6D6E1F</guid>
<short-name>wsanchez</short-name>
<short-name>wilfredo_sanchez</short-name>
<full-name>Wilfredo Sanchez</full-name>
@@ -435,7 +430,6 @@
<record type="user">
<uid>__glyph__</uid>
- <guid>9064DF91-1DBC-4E07-9C2B-6839B0953876</guid>
<short-name>glyph</short-name>
<full-name>Glyph Lefkowitz</full-name>
<password>hpylg</password>
@@ -445,7 +439,6 @@
<record type="user">
<uid>__sagen__</uid>
- <guid>4AD155CB-AE9B-475F-986C-E08A7537893E</guid>
<short-name>sagen</short-name>
<full-name>Morgen Sagen</full-name>
<password>negas</password>
@@ -455,7 +448,6 @@
<record type="user">
<uid>__cdaboo__</uid>
- <guid>7D45CB10-479E-456B-B54D-528958C5734B</guid>
<short-name>cdaboo</short-name>
<full-name>Cyrus Daboo</full-name>
<password>suryc</password>
@@ -464,7 +456,6 @@
<record type="user">
<uid>__dre__</uid>
- <guid>CFC88493-DBFF-42B9-ADC7-9B3DA0B0769B</guid>
<short-name>dre</short-name>
<full-name>Andre LaBranche</full-name>
<password>erd</password>
@@ -474,7 +465,6 @@
<record type="group">
<uid>__top_group_1__</uid>
- <guid>49B350C6-9611-477B-94D9-5516B13856AB</guid>
<short-name>top-group-1</short-name>
<full-name>Top Group 1</full-name>
<email>topgroup1 at example.com</email>
@@ -485,7 +475,6 @@
<record type="group">
<uid>__sub_group_1__</uid>
- <guid>86144F73-345A-4097-82F1-B782672087C7</guid>
<short-name>sub-group-1</short-name>
<full-name>Sub Group 1</full-name>
<email>subgroup1 at example.com</email>
Modified: CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txdav/who/xml.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -143,10 +143,14 @@
(BaseDirectoryService.recordType, RecordType)
)
- fieldName = ConstantsContainer(
- (BaseDirectoryService.fieldName, FieldName)
- )
+ # MOVE2WHO: Wilfredo had added augment fields into xml, which does make
+ # some sense, but for backwards compatibility right now I will take those
+ # out, and rely on a separate augment service
+ # fieldName = ConstantsContainer(
+ # (BaseDirectoryService.fieldName, FieldName)
+ # )
+
# XML schema constants
element = ConstantsContainer(
Modified: CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py
===================================================================
--- CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py 2014-03-05 18:09:23 UTC (rev 12818)
+++ CalendarServer/branches/users/sagen/move2who/txweb2/dav/resource.py 2014-03-05 18:24:32 UTC (rev 12819)
@@ -997,6 +997,7 @@
if the authentication scheme is unsupported, or the
credentials provided by the request are not valid.
"""
+
# Bypass normal authentication if its already been done (by SACL check)
if (
hasattr(request, "authnUser") and
@@ -1134,7 +1135,7 @@
# The default behaviour is no ACL; we should inherit from the parent
# collection.
#
- return element.ACL()
+ return succeed(element.ACL())
def setAccessControlList(self, acl):
@@ -1360,6 +1361,7 @@
@return: a L{Deferred} that callbacks with C{None} or errbacks
with an L{AccessDeniedError}
"""
+
if principal is None:
principal = self.currentPrincipal(request)
@@ -1509,7 +1511,7 @@
# If we get to the root without any ACLs, then use the default.
acl = self.defaultRootAccessControlList()
else:
- acl = self.defaultAccessControlList()
+ acl = yield self.defaultAccessControlList()
# Dynamically update privileges for those ace's that are inherited.
if inheritance:
@@ -1618,6 +1620,7 @@
return []
+ @inlineCallbacks
def principalsForAuthID(self, request, authid):
"""
Return authentication and authorization principal identifiers
@@ -1637,16 +1640,16 @@
HTTPError(responsecode.FORBIDDEN) if the principal isn't
found.
"""
- authnPrincipal = self.findPrincipalForAuthID(authid)
+ authnPrincipal = yield self.findPrincipalForAuthID(authid)
if authnPrincipal is None:
- return succeed((None, None))
+ returnValue((None, None))
- d = self.authorizationPrincipal(request, authid, authnPrincipal)
- d.addCallback(lambda authzPrincipal: (authnPrincipal, authzPrincipal))
- return d
+ authzPrincipal = yield self.authorizationPrincipal(request, authid, authnPrincipal)
+ returnValue((authnPrincipal, authzPrincipal))
+ @inlineCallbacks
def findPrincipalForAuthID(self, authid):
"""
Return authentication and authorization principal identifiers
@@ -1662,10 +1665,10 @@
found return None.
"""
for collection in self.principalCollections():
- principal = collection.principalForUser(authid)
+ principal = yield collection.principalForUser(authid)
if principal is not None:
- return principal
- return None
+ returnValue(principal)
+ returnValue(None)
def authorizationPrincipal(self, request, authid, authnPrincipal):
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140305/cf0f47a9/attachment-0001.html>
More information about the calendarserver-changes
mailing list