[CalendarServer-changes] [13362] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Wed Apr 23 09:47:07 PDT 2014


Revision: 13362
          http://trac.calendarserver.org//changeset/13362
Author:   cdaboo at apple.com
Date:     2014-04-23 09:47:07 -0700 (Wed, 23 Apr 2014)
Log Message:
-----------
Hide missing or invalid proxyFors. Fix some sharing issues related to missing or invalid users.

Modified Paths:
--------------
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/resource.py
    CalendarServer/trunk/twistedcaldav/sharing.py
    CalendarServer/trunk/twistedcaldav/test/test_sharing.py
    CalendarServer/trunk/twistedcaldav/test/util.py
    CalendarServer/trunk/txdav/common/datastore/test/util.py
    CalendarServer/trunk/txdav/who/directory.py

Removed Paths:
-------------
    CalendarServer/trunk/twistedcaldav/directory/test/util.py

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -986,7 +986,7 @@
 
 
     @inlineCallbacks
-    def proxyFor(self, readWrite):
+    def proxyFor(self, readWrite, ignoreDisabled=True):
         """
         Returns the set of principals currently delegating to this principal
         with the access indicated by the readWrite argument.  If readWrite is
@@ -995,7 +995,9 @@
 
         @param readWrite: Whether to look up read-write delegators, or
             read-only delegators
-        @type readWrite: C{bool}
+        @type readWrite: L{bool}
+        @param ignoreDisabled: If L{True} disabled delegators are not returned
+        @type ignoreDisabled: L{bool}
 
         @return: A Deferred firing with a set of principals
         """
@@ -1021,7 +1023,7 @@
 
                 for record in proxyForRecords:
                     principal = yield self.parent.principalForRecord(record)
-                    if principal is not None:
+                    if principal is not None and (not ignoreDisabled or principal.record.hasCalendars):
                         proxyFors.add(principal)
 
         returnValue(proxyFors)

Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2014-04-23 16:47:07 UTC (rev 13362)
@@ -66,12 +66,37 @@
     <enable-calendar>false</enable-calendar>
     <enable-addressbook>false</enable-addressbook>
   </record>
-  <record repeat="100">
-    <uid>user%02d</uid>
+  <record>
+    <uid>user01</uid>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
   </record>
   <record>
+    <uid>user02</uid>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>user03</uid>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>user04</uid>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>user05</uid>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
+    <uid>user06</uid>
+    <enable-calendar>true</enable-calendar>
+    <enable-addressbook>true</enable-addressbook>
+  </record>
+  <record>
     <uid>right_coast</uid>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -15,18 +15,11 @@
 ##
 from __future__ import print_function
 
-from urllib import quote
-from uuid import UUID
+from twext.who.idirectory import RecordType
 
+from twisted.cred.credentials import UsernamePassword
 from twisted.internet.defer import inlineCallbacks, returnValue
-from twisted.cred.credentials import UsernamePassword
 
-from txweb2.dav.resource import AccessDeniedError
-from txweb2.http import HTTPError
-from txweb2.test.test_server import SimpleRequest
-
-from txdav.xml import element as davxml
-
 from twistedcaldav import carddavxml
 from twistedcaldav.cache import DisabledCacheNotifier
 from twistedcaldav.caldavxml import caldav_namespace
@@ -38,10 +31,19 @@
     DirectoryPrincipalTypeProvisioningResource,
 )
 from twistedcaldav.test.util import StoreTestCase
+
+from txdav.who.delegates import addDelegate
 from txdav.who.idirectory import AutoScheduleMode, RecordType as CalRecordType
-from twext.who.idirectory import RecordType
+from txdav.xml import element as davxml
 
+from txweb2.dav.resource import AccessDeniedError
+from txweb2.http import HTTPError
+from txweb2.test.test_server import SimpleRequest
 
+from urllib import quote
+from uuid import UUID
+
+
 class ProvisionedPrincipals(StoreTestCase):
     """
     Directory service provisioned principals.
@@ -53,7 +55,6 @@
         self.principalRootResource = self.actualRoot.getChild("principals")
 
 
-
     @inlineCallbacks
     def test_hierarchy(self):
         """
@@ -169,7 +170,7 @@
         Test of a test routine...
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if True:  # user.enabled:
                 self.assertEquals(recordResource.record, record)
@@ -185,7 +186,7 @@
         DirectoryPrincipalProvisioningResource.principalForShortName()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
             principal = yield provisioningResource.principalForShortName(
                 recordType, record.shortNames[0]
@@ -246,7 +247,7 @@
         DirectoryPrincipalProvisioningResource.principalForUID()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, _ignore_recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
             principal = yield provisioningResource.principalForUID(record.uid)
             if True:  # user.enabled:
@@ -262,7 +263,7 @@
         DirectoryPrincipalProvisioningResource.principalForRecord()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, _ignore_recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
             principal = yield provisioningResource.principalForRecord(record)
             if True:  # user.enabled:
@@ -279,7 +280,7 @@
         .principalForCalendarUserAddress()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
 
             test_items = tuple(record.calendarUserAddresses)
@@ -347,7 +348,7 @@
         .principalForCalendarUserAddress()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, _ignore_recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
             principal = yield provisioningResource.principalForRecord(record)
             if True:  # user.enabled:
@@ -465,7 +466,7 @@
             self.directory.recordType.user,
         )
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
 
             if record.hasCalendars:
@@ -488,7 +489,7 @@
             self.directory.recordType.resource,
         )
         for (
-            provisioningResource, recordType, recordResource, record
+            provisioningResource, recordType, _ignore_recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 principal = (
@@ -515,7 +516,7 @@
         that is an instance of DisabledCacheNotifier
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, _ignore_record
         ) in (yield self._allRecords()):
             if True:  # user.enabled:
                 self.failUnless(
@@ -532,7 +533,7 @@
         DirectoryPrincipalResource.displayName()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, _ignore_record
         ) in (yield self._allRecords()):
             self.failUnless(recordResource.displayName())
 
@@ -543,7 +544,7 @@
         DirectoryPrincipalResource.groupMembers()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             members = yield recordResource.groupMembers()
             self.failUnless(
@@ -559,7 +560,7 @@
         DirectoryPrincipalResource.groupMemberships()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if True:  # user.enabled:
                 memberships = yield recordResource.groupMemberships()
@@ -579,7 +580,7 @@
         DirectoryPrincipalResource.principalUID()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             self.assertEquals(record.uid, recordResource.principalUID())
 
@@ -590,7 +591,7 @@
         DirectoryPrincipalResource.calendarUserAddresses()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 self.assertEqual(
@@ -609,7 +610,7 @@
         DirectoryPrincipalResource.canonicalCalendarUserAddress()
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 self.failUnless(
@@ -625,7 +626,7 @@
         """
 
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasContacts:
                 homeURLs = tuple(recordResource.addressBookHomeURLs())
@@ -684,7 +685,7 @@
 
         # Calendar home provisioners should result in calendar homes.
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 homeURLs = tuple(recordResource.calendarHomeURLs())
@@ -746,7 +747,7 @@
 
         # Default state - resources and locations, enabled, others not
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 if recordType in (CalRecordType.location, CalRecordType.resource):
@@ -757,7 +758,7 @@
         # Set config to allow users
         self.patch(config.Scheduling.Options.AutoSchedule, "AllowUsers", True)
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 if (
@@ -777,7 +778,7 @@
         # Set config to disallow all
         self.patch(config.Scheduling.Options.AutoSchedule, "Enabled", False)
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, recordType, recordResource, record
         ) in (yield self._allRecords()):
             if record.hasCalendars:
                 self.assertFalse((yield recordResource.canAutoSchedule()))
@@ -829,7 +830,7 @@
         Default access controls for principals.
         """
         for (
-            provisioningResource, recordType, recordResource, record
+            _ignore_provisioningResource, _ignore_recordType, recordResource, _ignore_record
         ) in (yield self._allRecords()):
             if True:  # user.enabled:
                 for args in (
@@ -1016,7 +1017,7 @@
 def _authReadOnlyPrivileges(self, resource, url):
     items = []
     for (
-        provisioningResource, recordType, recordResource, record
+        _ignore_provisioningResource, _ignore_recordType, recordResource, _ignore_record
     ) in (yield self._allRecords()):
         if True:  # user.enabled:
             items.append((
@@ -1039,3 +1040,62 @@
         results.append((resource, url, principal, privilege, allowed))
 
     returnValue(results)
+
+
+
+class ProxyPrincipals(StoreTestCase):
+    """
+    Directory service proxy principals.
+    """
+    @inlineCallbacks
+    def setUp(self):
+        yield super(ProxyPrincipals, self).setUp()
+
+        self.principalRootResource = self.actualRoot.getChild("principals")
+
+
+    @inlineCallbacks
+    def test_hideDisabledProxies(self):
+        """
+        Make sure users that are missing or not enabled for calendaring are removed
+        from the proxyFor list.
+        """
+
+        # Check proxies empty right now
+        principal01 = yield self.principalRootResource.principalForUID((yield self.userUIDFromShortName("user01")))
+        self.assertTrue(len((yield principal01.proxyFor(False))) == 0)
+        self.assertTrue(len((yield principal01.proxyFor(True))) == 0)
+
+        principal02 = yield self.principalRootResource.principalForUID((yield self.userUIDFromShortName("user02")))
+        self.assertTrue(len((yield principal02.proxyFor(False))) == 0)
+        self.assertTrue(len((yield principal02.proxyFor(True))) == 0)
+
+        principal03 = yield self.principalRootResource.principalForUID((yield self.userUIDFromShortName("user03")))
+        self.assertTrue(len((yield principal03.proxyFor(False))) == 0)
+        self.assertTrue(len((yield principal03.proxyFor(True))) == 0)
+
+        # Make user01 a read-only proxy for user02 and user03
+        yield addDelegate(self.transactionUnderTest(), principal02.record, principal01.record, False)
+        yield addDelegate(self.transactionUnderTest(), principal03.record, principal01.record, False)
+        yield self.commit()
+
+        self.assertTrue(len((yield principal01.proxyFor(False))) == 2)
+        self.assertTrue(len((yield principal01.proxyFor(True))) == 0)
+
+        # Now disable user02
+        yield self.changeRecord(principal02.record, self.directory.fieldName.hasCalendars, False)
+
+        self.assertTrue(len((yield principal01.proxyFor(False))) == 1)
+        self.assertTrue(len((yield principal01.proxyFor(True))) == 0)
+
+        # Now enable user02
+        yield self.changeRecord(principal02.record, self.directory.fieldName.hasCalendars, True)
+
+        self.assertTrue(len((yield principal01.proxyFor(False))) == 2)
+        self.assertTrue(len((yield principal01.proxyFor(True))) == 0)
+
+        # Now remove user02
+        yield self.directory.removeRecords((principal02.record.uid,))
+
+        self.assertTrue(len((yield principal01.proxyFor(False))) == 1)
+        self.assertTrue(len((yield principal01.proxyFor(True))) == 0)

Deleted: CalendarServer/trunk/twistedcaldav/directory/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/util.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/directory/test/util.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -1,386 +0,0 @@
-##
-# Copyright (c) 2005-2014 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.
-##
-
-from twisted.trial.unittest import SkipTest
-from twisted.cred.credentials import UsernamePassword
-from txweb2.auth.digest import DigestedCredentials, calcResponse, calcHA1
-
-from twistedcaldav.directory.directory import DirectoryService
-from twistedcaldav.directory.directory import UnknownRecordTypeError
-from twistedcaldav.directory.util import transactionFromRequest
-from twistedcaldav.test.util import TestCase
-
-# FIXME: Add tests for GUID hooey, once we figure out what that means here
-
-class DirectoryTestCase (TestCase):
-    """
-    Tests a directory implementation.
-    """
-    # Subclass should init this to a set of recordtypes.
-    recordTypes = set()
-
-    # Subclass should init this to a dict of username keys and dict values.
-    users = {}
-
-    # Subclass should init this to a dict of groupname keys and dict values.
-    groups = {}
-
-    # Subclass should init this to a dict of locationnames keys and dict values.
-    locations = {}
-
-    # Subclass should init this to a dict of resourcenames keys and dict values.
-    resources = {}
-
-    # Subclass should init this to a dict of addressname keys and dict values.
-    addresses = {}
-
-
-    # Subclass should init this to an IDirectoryService implementation class.
-    def service(self):
-        """
-        Returns an IDirectoryService.
-        """
-        raise NotImplementedError("Subclass needs to implement service()")
-
-    # For aggregator subclasses
-    recordTypePrefixes = ("",)
-
-
-    def test_realm(self):
-        """
-        IDirectoryService.realm
-        """
-        self.failUnless(self.service().realmName)
-
-
-    def test_recordTypes(self):
-        """
-        IDirectoryService.recordTypes()
-        """
-        if not self.recordTypes:
-            raise SkipTest("No record types")
-
-        self.assertEquals(set(self.service().recordTypes()), self.recordTypes)
-
-
-    def test_recordWithShortName(self):
-        """
-        IDirectoryService.recordWithShortName()
-        """
-        for recordType, data in (
-            (DirectoryService.recordType_users    , self.users),
-            (DirectoryService.recordType_groups   , self.groups),
-            (DirectoryService.recordType_locations, self.locations),
-            (DirectoryService.recordType_resources, self.resources),
-        ):
-            if not data:
-                raise SkipTest("No %s" % (recordType,))
-
-            service = self.service()
-            for shortName, info in data.iteritems():
-                record = service.recordWithShortName(info.get("prefix", "") + recordType, shortName)
-                self.failUnless(record, "No record (%s)%s" % (info.get("prefix", "") + recordType, shortName))
-                self.compare(record, shortName, data[shortName])
-
-            for prefix in self.recordTypePrefixes:
-                try:
-                    record = service.recordWithShortName(prefix + recordType, "IDunnoWhoThisIsIReallyDont")
-                except UnknownRecordTypeError:
-                    continue
-                self.assertEquals(record, None)
-
-
-    def test_recordWithUID(self):
-        service = self.service()
-        record = None
-
-        for shortName, what in self.allEntries():
-            guid = what["guid"]
-            if guid is not None:
-                record = service.recordWithUID(guid)
-                self.compare(record, shortName, what)
-
-        if record is None:
-            raise SkipTest("No GUIDs provided to test")
-
-
-    def test_recordWithCalendarUserAddress(self):
-        service = self.service()
-        record = None
-
-        for shortName, what in self.allEntries():
-            for address in what["addresses"]:
-                record = service.recordWithCalendarUserAddress(address)
-                self.compare(record, shortName, what)
-
-        if record is None:
-            raise SkipTest("No calendar user addresses provided to test")
-
-
-    def test_groupMembers(self):
-        """
-        IDirectoryRecord.members()
-        """
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        service = self.service()
-        for group, info in self.groups.iteritems():
-            prefix = info.get("prefix", "")
-            groupRecord = service.recordWithShortName(prefix + DirectoryService.recordType_groups, group)
-            result = set((m.recordType, prefix + m.shortNames[0]) for m in groupRecord.members())
-            expected = set(self.groups[group]["members"])
-            self.assertEquals(
-                result, expected,
-                "Wrong membership for group %r: %s != %s" % (group, result, expected)
-            )
-
-
-    def test_groupMemberships(self):
-        """
-        IDirectoryRecord.groups()
-        """
-        if not self.users:
-            raise SkipTest("No users")
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        for recordType, data in (
-            (DirectoryService.recordType_users , self.users),
-            (DirectoryService.recordType_groups, self.groups),
-        ):
-            service = self.service()
-            for shortName, info in data.iteritems():
-                prefix = info.get("prefix", "")
-                record = service.recordWithShortName(prefix + recordType, shortName)
-                result = set(prefix + g.shortNames[0] for g in record.groups())
-                expected = set(g for g in self.groups if (record.recordType, shortName) in self.groups[g]["members"])
-                self.assertEquals(
-                    result, expected,
-                    "Wrong groups for %s %r: %s != %s" % (record.recordType, shortName, result, expected)
-                )
-
-
-    def recordNames(self, recordType):
-        service = self.service()
-        names = set()
-        for prefix in self.recordTypePrefixes:
-            try:
-                records = service.listRecords(prefix + recordType)
-            except UnknownRecordTypeError:
-                continue
-            assert records is not None, "%r(%r) returned None" % (service.listRecords, recordType)
-            for record in records:
-                names.add(prefix + record.shortNames[0])
-
-        return names
-
-
-    def allEntries(self):
-        for data, _ignore_recordType in (
-            (self.users, DirectoryService.recordType_users),
-            (self.groups, DirectoryService.recordType_groups),
-            (self.locations, DirectoryService.recordType_locations),
-            (self.resources, DirectoryService.recordType_resources),
-        ):
-            for item in data.iteritems():
-                yield item
-
-
-    def compare(self, record, shortName, data):
-        def value(key):
-            if key in data:
-                return data[key]
-            else:
-                return None
-
-        guid = value("guid")
-        if guid is not None:
-            guid = record.guid
-
-        addresses = set(value("addresses"))
-        if record.hasCalendars:
-            addresses.add("urn:x-uid:%s" % (record.uid,))
-            addresses.add("urn:uuid:%s" % (record.guid,))
-            addresses.add("/principals/__uids__/%s/" % (record.uid,))
-            addresses.add("/principals/%s/%s/" % (record.recordType, record.shortNames[0],))
-
-        if hasattr(record.service, "recordTypePrefix"):
-            prefix = record.service.recordTypePrefix
-        else:
-            prefix = ""
-
-        self.assertEquals(prefix + record.shortNames[0], shortName)
-        self.assertEquals(set(record.calendarUserAddresses), addresses)
-
-        if value("guid"):
-            self.assertEquals(record.guid, value("guid"))
-
-        if value("name"):
-            self.assertEquals(record.fullName, value("name"))
-
-
-    def servicePrefix(self):
-        service = self.service()
-        if hasattr(service, "recordTypePrefix"):
-            return service.recordTypePrefix
-        else:
-            return ""
-
-
-
-class NonCachingTestCase (DirectoryTestCase):
-
-    def test_listRecords_user(self):
-        """
-        IDirectoryService.listRecords(DirectoryService.recordType_users)
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        self.assertEquals(self.recordNames(DirectoryService.recordType_users), set(self.users.keys()))
-
-
-    def test_listRecords_group(self):
-        """
-        IDirectoryService.listRecords(DirectoryService.recordType_groups)
-        """
-        if not self.groups:
-            raise SkipTest("No groups")
-
-        self.assertEquals(self.recordNames(DirectoryService.recordType_groups), set(self.groups.keys()))
-
-
-    def test_listRecords_locations(self):
-        """
-        IDirectoryService.listRecords("locations")
-        """
-        if not self.resources:
-            raise SkipTest("No locations")
-
-        self.assertEquals(self.recordNames(DirectoryService.recordType_locations), set(self.locations.keys()))
-
-
-    def test_listRecords_resources(self):
-        """
-        IDirectoryService.listRecords("resources")
-        """
-        if not self.resources:
-            raise SkipTest("No resources")
-
-        self.assertEquals(self.recordNames(DirectoryService.recordType_resources), set(self.resources.keys()))
-
-
-
-class BasicTestCase (DirectoryTestCase):
-    """
-    Tests a directory implementation with basic auth.
-    """
-    def test_verifyCredentials_basic(self):
-        """
-        IDirectoryRecord.verifyCredentials() with basic
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        service = self.service()
-        for user in self.users:
-            userRecord = service.recordWithShortName(DirectoryService.recordType_users, user)
-            self.failUnless(userRecord.verifyCredentials(UsernamePassword(user, self.users[user]["password"])))
-
-
-
-# authRequest = {
-#    username="username",
-#    realm="test realm",
-#    nonce="178288758716122392881254770685",
-#    uri="/write/",
-#    response="62f388be1cf678fbdfce87910871bcc5",
-#    opaque="1041524039",
-#    algorithm="md5",
-#    cnonce="29fc54aa1641c6fa0e151419361c8f23",
-#    nc=00000001,
-#    qop="auth",
-# }
-
-class DigestTestCase (DirectoryTestCase):
-    """
-    Tests a directory implementation with digest auth.
-    """
-    def test_verifyCredentials_digest(self):
-        """
-        IDirectoryRecord.verifyCredentials() with digest
-        """
-        if not self.users:
-            raise SkipTest("No users")
-
-        service = self.service()
-        for user in self.users:
-            for good in (True, True, False, False, True):
-                userRecord = service.recordWithShortName(DirectoryService.recordType_users, user)
-
-                # I'm glad this is so simple...
-                response = calcResponse(
-                    calcHA1(
-                        "md5",
-                        user,
-                        service.realmName,
-                        self.users[user]["password"],
-                        "booger",
-                        "phlegm",
-                    ),
-                    "md5",
-                    "booger",
-                    None,
-                    "phlegm",
-                    "auth",
-                    "GET",
-                    "/",
-                    None,
-                )
-
-                if good:
-                    noise = ""
-                else:
-                    noise = "blah"
-
-                credentials = DigestedCredentials(
-                    user,
-                    "GET",
-                    service.realmName,
-                    {
-                        "response": response,
-                        "uri": "/",
-                        "nonce": "booger" + noise,
-                        "cnonce": "phlegm",
-                        "nc": None,
-                    },
-                )
-
-                if good:
-                    self.failUnless(userRecord.verifyCredentials(credentials))
-                else:
-                    self.failIf(userRecord.verifyCredentials(credentials))
-
-
-
-def maybeCommit(req):
-    class JustForCleanup(object):
-        def newTransaction(self, *whatever):
-            return self
-        def commit(self):
-            return
-    transactionFromRequest(req, JustForCleanup()).commit()

Modified: CalendarServer/trunk/twistedcaldav/resource.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/resource.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/resource.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -2567,7 +2567,10 @@
     @inlineCallbacks
     def _otherPrincipalHomeURL(self, otherUID):
         ownerPrincipal = (yield self.principalForUID(otherUID))
-        returnValue(ownerPrincipal.calendarHomeURLs()[0])
+        if ownerPrincipal and len(ownerPrincipal.calendarHomeURLs()):
+            returnValue(ownerPrincipal.calendarHomeURLs()[0])
+        else:
+            returnValue(None)
 
 
     @inlineCallbacks
@@ -2822,7 +2825,7 @@
     @inlineCallbacks
     def _otherPrincipalHomeURL(self, otherUID):
         ownerPrincipal = (yield self.principalForUID(otherUID))
-        returnValue(ownerPrincipal.addressBookHomeURLs()[0])
+        returnValue(ownerPrincipal.addressBookHomeURLs()[0] if ownerPrincipal else None)
 
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/sharing.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/sharing.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -91,12 +91,16 @@
                     invitations = yield self.validateInvites(request, invitations)
 
                     ownerPrincipal = yield self.principalForUID(self._newStoreObject.ownerHome().uid())
-                    # FIXME:  use urn:x-uid in all cases
-                    if self.isCalendarCollection():
-                        owner = ownerPrincipal.principalURL()
+                    if ownerPrincipal is None:
+                        owner = "invalid"
+                        ownerCN = "Invalid"
                     else:
-                        owner = "urn:x-uid:" + ownerPrincipal.principalUID()
-                    ownerCN = ownerPrincipal.displayName()
+                        # FIXME:  use urn:x-uid in all cases
+                        if self.isCalendarCollection():
+                            owner = ownerPrincipal.principalURL()
+                        else:
+                            owner = "urn:x-uid:" + ownerPrincipal.principalUID()
+                        ownerCN = ownerPrincipal.displayName()
 
                     returnValue(customxml.Invite(
                         customxml.Organizer(
@@ -435,12 +439,15 @@
         # assert request
         if invitations is None:
             invitations = yield self._newStoreObject.allInvitations()
+        adjusted_invitations = []
         for invitation in invitations:
             if invitation.status != _BIND_STATUS_INVALID:
                 if not (yield self.validUserIDForShare("urn:x-uid:" + invitation.shareeUID, request)):
                     self.log.error("Invalid sharee detected: {uid}", uid=invitation.shareeUID)
+                    invitation = invitation._replace(status=_BIND_STATUS_INVALID)
+            adjusted_invitations.append(invitation)
 
-        returnValue(invitations)
+        returnValue(adjusted_invitations)
 
 
     def inviteUserToShare(self, userid, cn, ace, summary, request):
@@ -519,12 +526,18 @@
     @inlineCallbacks
     def uninviteSingleUserFromShare(self, userid, aces, request): #@UnusedVariable
 
-        # Cancel invites - we'll just use whatever userid we are given
+        # Cancel invites - we'll just use whatever userid we are given. However, if we
+        # cannot find a matching principal, try to extract the uid from the userid
+        # and use that (to allow invalid principals to be removed).
         sharee = yield self.principalForCalendarUserAddress(userid)
-        if not sharee:
+        if sharee is not None:
+            uid = sharee.principalUID()
+        elif userid.startswith("urn:x-uid:"):
+            uid = userid[10:]
+        else:
             returnValue(False)
 
-        result = (yield self._newStoreObject.uninviteUserFromShare(sharee.principalUID()))
+        result = (yield self._newStoreObject.uninviteUserFromShare(uid))
 
         returnValue(result)
 
@@ -793,7 +806,7 @@
         if child._newStoreObject is not None and not child._newStoreObject.owned():
             ownerHomeURL = (yield self._otherPrincipalHomeURL(child._newStoreObject.ownerHome().uid()))
             ownerView = yield child._newStoreObject.ownerView()
-            child.setShare(joinURL(ownerHomeURL, ownerView.name()))
+            child.setShare(joinURL(ownerHomeURL, ownerView.name()) if ownerHomeURL else None)
             access = yield child._checkAccessControl()
             if access is None:
                 returnValue(None)

Modified: CalendarServer/trunk/twistedcaldav/test/test_sharing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/test/test_sharing.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -23,7 +23,6 @@
 
 from twistedcaldav import customxml
 from twistedcaldav.config import config
-from twistedcaldav.directory.principal import DirectoryCalendarPrincipalResource
 from twistedcaldav.test.test_cache import StubResponseCacheResource
 from twistedcaldav.test.util import norequest, StoreTestCase, SimpleStoreRequest
 
@@ -35,7 +34,6 @@
 from txdav.who.wiki import (
     DirectoryRecord as WikiDirectoryRecord,
     DirectoryService as WikiDirectoryService,
-    RecordType as WikiRecordType,
     WikiAccessLevel
 )
 
@@ -53,78 +51,6 @@
 
 
 
-class FakeHome(object):
-    pass
-
-
-
-class FakeRecord(object):
-
-    def __init__(self, name, cuaddr):
-        self.fullName = name
-        self.guid = name
-        self.calendarUserAddresses = set((cuaddr,))
-        if name.startswith(WikiDirectoryService.uidPrefix):
-            recordType = WikiRecordType.macOSXServerWiki
-        else:
-            recordType = None
-        self.recordType = recordType
-        self.shortNames = [name]
-
-
-
-class FakePrincipal(DirectoryCalendarPrincipalResource):
-
-    invalid_names = set()
-
-    def __init__(self, cuaddr, test):
-        if cuaddr.startswith("mailto:"):
-            name = cuaddr[7:].split('@')[0]
-        elif cuaddr.startswith("urn:x-uid:"):
-            name = cuaddr[10:]
-        elif cuaddr.startswith("urn:uuid:"):
-            name = cuaddr[9:]
-        else:
-            name = cuaddr
-
-        self.path = "/principals/__uids__/%s" % (name,)
-        self.homepath = "/calendars/__uids__/%s" % (name,)
-        self.displayname = name.upper()
-        self.record = FakeRecord(name, cuaddr)
-        self._test = test
-        self._name = name
-
-
-    @inlineCallbacks
-    def calendarHome(self, request):
-        if self._name in self.invalid_names:
-            returnValue(None)
-        a, _ignore_seg = yield self._test.calendarCollection.locateChild(request, ["__uids__"])
-        b, _ignore_seg = yield a.locateChild(request, [self._name])
-        if b is None:
-            # XXX all tests except test_noWikiAccess currently rely on the
-            # fake thing here.
-            returnValue(FakeHome())
-        returnValue(b)
-
-
-    def calendarHomeURLs(self):
-        return (self.homepath,)
-
-
-    def principalURL(self):
-        return self.path
-
-
-    def principalUID(self):
-        return self.record.guid
-
-
-    def displayName(self):
-        return self.displayname
-
-
-
 class SharingTests(StoreTestCase):
 
     def configure(self):
@@ -140,39 +66,6 @@
     @inlineCallbacks
     def setUp(self):
         yield super(SharingTests, self).setUp()
-
-        # FIXME: not sure what these were for:
-
-        #     def patched(c):
-        #         """
-        #         The decorated method is patched on L{CalDAVResource} for the
-        #         duration of the test.
-        #         """
-        #         self.patch(CalDAVResource, c.__name__, c)
-        #         return c
-
-        #     @patched
-        #     def principalForCalendarUserAddress(resourceSelf, cuaddr):
-        #         if "bogus" in cuaddr:
-        #             return None
-        #         else:
-        #             return FakePrincipal(cuaddr, self)
-
-        #     @patched
-        #     def validUserIDForShare(resourceSelf, userid, request):
-        #         """
-        #         Temporary replacement for L{CalDAVResource.validUserIDForShare}
-        #         that marks any principal without 'bogus' in its name.
-        #         """
-        #         result = principalForCalendarUserAddress(resourceSelf, userid)
-        #         if result is None:
-        #             return result
-        #         return result.principalURL()
-
-        #     @patched
-        #     def principalForUID(resourceSelf, principalUID):
-        #         return FakePrincipal("urn:uuid:" + principalUID, self)
-
         self.resource = yield self._getResource()
 
 
@@ -216,9 +109,9 @@
 
 
     @inlineCallbacks
-    def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK):
-        authRecord = yield self.directory.recordWithUID(u"user02")
-        request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user02/", content=body, authRecord=authRecord)
+    def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK, sharer="user02"):
+        authRecord = yield self.directory.recordWithUID(unicode(sharer))
+        request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/{}/".format(sharer), content=body, authRecord=authRecord)
         request.headers.setHeader("content-type", MimeType("text", "xml"))
         response = yield self.send(request)
         response = IResponse(response)
@@ -248,12 +141,22 @@
         return None
 
 
+    def _getUIDElementValues(self, xml):
+
+        results = {}
+        for user in xml.children:
+            href = str(user.childOfType(davxml.HRef))
+            uid = str(user.childOfType(customxml.UID))
+            results[href] = uid
+        return results
+
+
     def _clearUIDElementValue(self, xml):
 
         for user in xml.children:
-            for element in user.children:
-                if type(element) == customxml.UID:
-                    element.children[0].data = ""
+            uid = user.childOfType(customxml.UID)
+            if uid is not None:
+                uid.children[0].data = ""
         return xml
 
 
@@ -713,25 +616,24 @@
             )
         ))
 
-        self.resource.validUserIDForShare = lambda userid, request: None
-        self.resource.principalForCalendarUserAddress = lambda cuaddr: None
-        self.resource.principalForUID = lambda principalUID: None
+        record = yield self.userRecordWithShortName("user02")
+        yield self.changeRecord(record, self.directory.fieldName.hasCalendars, False)
 
         propInvite = (yield self.resource.readProperty(customxml.Invite, None))
         self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
             customxml.InviteUser(
                 customxml.UID.fromString(""),
                 davxml.HRef.fromString("urn:x-uid:user02"),
-                customxml.CommonName.fromString("user02"),
+                customxml.CommonName.fromString("User 02"),
                 customxml.InviteAccess(customxml.ReadWriteAccess()),
-                customxml.InviteStatusNoResponse(),
+                customxml.InviteStatusInvalid(),
             )
         ))
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
             <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
                 <CS:remove>
-                    <D:href>mailto:user02 at example.com</D:href>
+                    <D:href>urn:x-uid:user02</D:href>
                 </CS:remove>
             </CS:share>
             """)
@@ -832,7 +734,7 @@
 
 
     @inlineCallbacks
-    def test_POSTDowngradeWithDisabledInvitee(self):
+    def test_POSTDowngradeWithMissingInvitee(self):
 
         yield self.resource.upgradeToShare()
 
@@ -857,12 +759,14 @@
             ),
         ))
 
-        self.patch(FakePrincipal, "invalid_names", set(("user02",)))
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName("user02")),))
+        self.assertTrue((yield self.userUIDFromShortName("user02")) is None)
+
         yield self.resource.downgradeFromShare(norequest())
 
 
     @inlineCallbacks
-    def test_POSTRemoveWithDisabledInvitee(self):
+    def test_POSTRemoveWithMissingInvitee(self):
 
         yield self.resource.upgradeToShare()
 
@@ -887,12 +791,13 @@
             ),
         ))
 
-        self.patch(FakePrincipal, "invalid_names", set(("user02",)))
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName("user02")),))
+        self.assertTrue((yield self.userUIDFromShortName("user02")) is None)
 
         yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
             <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
                 <CS:remove>
-                    <D:href>mailto:user02 at example.com</D:href>
+                    <D:href>urn:x-uid:user02</D:href>
                 </CS:remove>
             </CS:share>
             """)
@@ -948,10 +853,286 @@
         )
         href = self._getHRefElementValue(result) + "/"
 
-        self.patch(FakePrincipal, "invalid_names", set(("user01",)))
+        record = yield self.userRecordWithShortName("user01")
+        yield self.changeRecord(record, self.directory.fieldName.hasCalendars, False)
 
         resource = (yield self._getResourceSharer(href))
         yield resource.removeShareeResource(SimpleStoreRequest(self, "DELETE", href))
 
         resource = (yield self._getResourceSharer(href))
         self.assertFalse(resource.exists())
+
+
+    @inlineCallbacks
+    def test_POSTShareeRemoveWithMissingSharer(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+            <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+                <CS:set>
+                    <D:href>mailto:user02 at example.com</D:href>
+                    <CS:summary>My Shared Calendar</CS:summary>
+                    <CS:read-write/>
+                </CS:set>
+            </CS:share>
+            """)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uid = self._getUIDElementValue(propInvite)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusNoResponse(),
+            ),
+        ))
+
+        result = (yield self._doPOSTSharerAccept("""<?xml version='1.0' encoding='UTF-8'?>
+            <invite-reply xmlns='http://calendarserver.org/ns/'>
+              <href xmlns='DAV:'>mailto:user01 at example.com</href>
+              <invite-accepted/>
+              <hosturl>
+                <href xmlns='DAV:'>/calendars/__uids__/user01/calendar/</href>
+              </hosturl>
+              <in-reply-to>%s</in-reply-to>
+              <summary>The Shared Calendar</summary>
+              <common-name>User 02</common-name>
+              <first-name>user</first-name>
+              <last-name>02</last-name>
+            </invite-reply>
+            """ % (uid,))
+        )
+        href = self._getHRefElementValue(result) + "/"
+
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName("user01")),))
+        self.assertTrue((yield self.userUIDFromShortName("user01")) is None)
+
+        resource = (yield self._getResourceSharer(href))
+        yield resource.removeShareeResource(SimpleStoreRequest(self, "DELETE", href))
+
+        resource = (yield self._getResourceSharer(href))
+        self.assertFalse(resource.exists())
+
+
+    @inlineCallbacks
+    def test_shareeInviteWithDisabledSharer(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+            <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+                <CS:set>
+                    <D:href>mailto:user02 at example.com</D:href>
+                    <CS:summary>My Shared Calendar</CS:summary>
+                    <CS:read-write/>
+                </CS:set>
+            </CS:share>
+            """)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uid = self._getUIDElementValue(propInvite)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusNoResponse(),
+            ),
+        ))
+
+        result = (yield self._doPOSTSharerAccept("""<?xml version='1.0' encoding='UTF-8'?>
+            <invite-reply xmlns='http://calendarserver.org/ns/'>
+              <href xmlns='DAV:'>mailto:user01 at example.com</href>
+              <invite-accepted/>
+              <hosturl>
+                <href xmlns='DAV:'>/calendars/__uids__/user01/calendar/</href>
+              </hosturl>
+              <in-reply-to>%s</in-reply-to>
+              <summary>The Shared Calendar</summary>
+              <common-name>User 02</common-name>
+              <first-name>user</first-name>
+              <last-name>02</last-name>
+            </invite-reply>
+            """ % (uid,))
+        )
+        href = self._getHRefElementValue(result) + "/"
+
+        record = yield self.userRecordWithShortName("user01")
+        yield self.changeRecord(record, self.directory.fieldName.hasCalendars, False)
+
+        resource = (yield self._getResourceSharer(href))
+        propInvite = yield resource.inviteProperty(None)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.Organizer(
+                davxml.HRef.fromString("/principals/__uids__/user01/"),
+                customxml.CommonName.fromString("User 01"),
+            ),
+            customxml.InviteUser(
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusAccepted(),
+            ),
+        ))
+
+
+    @inlineCallbacks
+    def test_shareeInviteWithMissingSharer(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+            <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+                <CS:set>
+                    <D:href>mailto:user02 at example.com</D:href>
+                    <CS:summary>My Shared Calendar</CS:summary>
+                    <CS:read-write/>
+                </CS:set>
+            </CS:share>
+            """)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uid = self._getUIDElementValue(propInvite)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusNoResponse(),
+            ),
+        ))
+
+        result = (yield self._doPOSTSharerAccept("""<?xml version='1.0' encoding='UTF-8'?>
+            <invite-reply xmlns='http://calendarserver.org/ns/'>
+              <href xmlns='DAV:'>mailto:user01 at example.com</href>
+              <invite-accepted/>
+              <hosturl>
+                <href xmlns='DAV:'>/calendars/__uids__/user01/calendar/</href>
+              </hosturl>
+              <in-reply-to>%s</in-reply-to>
+              <summary>The Shared Calendar</summary>
+              <common-name>User 02</common-name>
+              <first-name>user</first-name>
+              <last-name>02</last-name>
+            </invite-reply>
+            """ % (uid,))
+        )
+        href = self._getHRefElementValue(result) + "/"
+
+        yield self.directory.removeRecords(((yield self.userUIDFromShortName("user01")),))
+        self.assertTrue((yield self.userUIDFromShortName("user01")) is None)
+
+        resource = (yield self._getResourceSharer(href))
+        propInvite = yield resource.inviteProperty(None)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.Organizer(
+                davxml.HRef.fromString("invalid"),
+                customxml.CommonName.fromString("Invalid"),
+            ),
+            customxml.InviteUser(
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusAccepted(),
+            ),
+        ))
+
+
+    @inlineCallbacks
+    def test_hideInvalidSharers(self):
+
+        yield self.resource.upgradeToShare()
+
+        yield self._doPOST("""<?xml version="1.0" encoding="utf-8" ?>
+            <CS:share xmlns:D="DAV:" xmlns:CS="http://calendarserver.org/ns/">
+                <CS:set>
+                    <D:href>mailto:user02 at example.com</D:href>
+                    <CS:summary>My Shared Calendar</CS:summary>
+                    <CS:read-write/>
+                </CS:set>
+                <CS:set>
+                    <D:href>mailto:user03 at example.com</D:href>
+                    <CS:summary>My Shared Calendar</CS:summary>
+                    <CS:read-write/>
+                </CS:set>
+            </CS:share>
+            """)
+
+        propInvite = (yield self.resource.readProperty(customxml.Invite, None))
+        uids = self._getUIDElementValues(propInvite)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusNoResponse(),
+            ),
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user03"),
+                customxml.CommonName.fromString("User 03"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusNoResponse(),
+            ),
+        ))
+
+        yield self._doPOSTSharerAccept("""<?xml version='1.0' encoding='UTF-8'?>
+            <invite-reply xmlns='http://calendarserver.org/ns/'>
+              <href xmlns='DAV:'>mailto:user01 at example.com</href>
+              <invite-accepted/>
+              <hosturl>
+                <href xmlns='DAV:'>/calendars/__uids__/user01/calendar/</href>
+              </hosturl>
+              <in-reply-to>%s</in-reply-to>
+              <summary>The Shared Calendar</summary>
+              <common-name>User 02</common-name>
+              <first-name>user</first-name>
+              <last-name>02</last-name>
+            </invite-reply>
+            """ % (uids["urn:x-uid:user02"],))
+
+        yield self._doPOSTSharerAccept(
+            """<?xml version='1.0' encoding='UTF-8'?>
+                <invite-reply xmlns='http://calendarserver.org/ns/'>
+                  <href xmlns='DAV:'>mailto:user01 at example.com</href>
+                  <invite-accepted/>
+                  <hosturl>
+                    <href xmlns='DAV:'>/calendars/__uids__/user01/calendar/</href>
+                  </hosturl>
+                  <in-reply-to>%s</in-reply-to>
+                  <summary>The Shared Calendar</summary>
+                  <common-name>User 03</common-name>
+                  <first-name>user</first-name>
+                  <last-name>03</last-name>
+                </invite-reply>
+            """ % (uids["urn:x-uid:user03"],),
+            sharer="user03"
+        )
+
+        record = yield self.directory.recordWithUID((yield self.userUIDFromShortName("user02")))
+        yield self.changeRecord(record, self.directory.fieldName.hasCalendars, False)
+
+        resource = yield self._getResource()
+        propInvite = yield resource.inviteProperty(None)
+        self.assertEquals(self._clearUIDElementValue(propInvite), customxml.Invite(
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user02"),
+                customxml.CommonName.fromString("User 02"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusInvalid(),
+            ),
+            customxml.InviteUser(
+                customxml.UID.fromString(""),
+                davxml.HRef.fromString("urn:x-uid:user03"),
+                customxml.CommonName.fromString("User 03"),
+                customxml.InviteAccess(customxml.ReadWriteAccess()),
+                customxml.InviteStatusAccepted(),
+            ),
+        ))

Modified: CalendarServer/trunk/twistedcaldav/test/util.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/test/util.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/twistedcaldav/test/util.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -77,8 +77,6 @@
 
 
 
-
-
 class SimpleStoreRequest(SimpleRequest):
     """
     A SimpleRequest that automatically grabs the proper transaction for a test.
@@ -190,7 +188,6 @@
         proxies.setContent(proxiesFile.getContent())
 
 
-
     def createHierarchy(self, structure, root=None):
         if root is None:
             root = os.path.abspath(self.mktemp())
@@ -407,7 +404,6 @@
         config.UsePackageTimezones = True
 
 
-
     def setUp(self):
         super(TestCase, self).setUp()
 
@@ -430,7 +426,6 @@
 
 
 
-
 class norequest(object):
     def addResponseFilter(self, filter):
         "stub; ignore me"

Modified: CalendarServer/trunk/txdav/common/datastore/test/util.py
===================================================================
--- CalendarServer/trunk/txdav/common/datastore/test/util.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/txdav/common/datastore/test/util.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -38,10 +38,9 @@
 
 from twext.python.log import Logger
 from twext.python.filepath import CachingFilePath
-from twistedcaldav.ical import Component as VComponent, Component
 from twext.enterprise.adbapi2 import ConnectionPool
 from twext.enterprise.ienterprise import AlreadyFinishedError
-from txweb2.dav.resource import TwistedGETContentMD5
+from twext.who.directory import DirectoryRecord
 
 from twisted.application.service import Service
 from twisted.internet import reactor
@@ -52,6 +51,7 @@
 
 from twistedcaldav import ical
 from twistedcaldav.config import config
+from twistedcaldav.ical import Component as VComponent, Component
 from twistedcaldav.stdconfig import DEFAULT_CONFIG
 from twistedcaldav.vcard import Component as ABComponent
 
@@ -63,6 +63,8 @@
 from txdav.common.datastore.sql_tables import schema
 from txdav.common.icommondatastore import NoSuchHomeChildError
 
+from txweb2.dav.resource import TwistedGETContentMD5
+
 from zope.interface.exceptions import BrokenMethodImplementation, \
     DoesNotImplement
 from zope.interface.verify import verifyObject
@@ -823,7 +825,27 @@
                     .addressbookObjectWithName(name)))
 
 
+    @inlineCallbacks
+    def userRecordWithShortName(self, shortname):
+        record = yield self.directory.recordWithShortName(self.directory.recordType.user, shortname)
+        returnValue(record)
 
+
+    @inlineCallbacks
+    def userUIDFromShortName(self, shortname):
+        record = yield self.directory.recordWithShortName(self.directory.recordType.user, shortname)
+        returnValue(record.uid if record is not None else None)
+
+
+    @inlineCallbacks
+    def changeRecord(self, record, fieldname, value):
+        fields = record.fields.copy()
+        fields[fieldname] = value
+        updatedRecord = DirectoryRecord(self.directory, fields)
+        yield self.directory.updateRecords((updatedRecord,))
+
+
+
 class StubNotifierFactory(object):
     """
     For testing push notifications without an XMPP server.

Modified: CalendarServer/trunk/txdav/who/directory.py
===================================================================
--- CalendarServer/trunk/txdav/who/directory.py	2014-04-23 00:49:34 UTC (rev 13361)
+++ CalendarServer/trunk/txdav/who/directory.py	2014-04-23 16:47:07 UTC (rev 13362)
@@ -377,9 +377,6 @@
 
 
     def enabledAsOrganizer(self):
-        # FIXME:
-        from twistedcaldav.config import config
-
         if self.recordType == self.service.recordType.user:
             return True
         elif self.recordType == self.service.recordType.group:
@@ -396,9 +393,6 @@
         """
         URL of the server hosting this record. Return None if hosted on this server.
         """
-        # FIXME:
-        from twistedcaldav.config import config
-
         if config.Servers.Enabled and getattr(self, "serviceNodeUID", None):
             return Servers.getServerURIById(self.serviceNodeUID)
         else:
@@ -409,9 +403,6 @@
         """
         Server hosting this record. Return None if hosted on this server.
         """
-        # FIXME:
-        from twistedcaldav.config import config
-
         if config.Servers.Enabled and getattr(self, "serviceNodeUID", None):
             return Servers.getServerById(self.serviceNodeUID)
         else:
@@ -428,17 +419,11 @@
 
 
     def calendarsEnabled(self):
-        # FIXME:
-        from twistedcaldav.config import config
-
         return config.EnableCalDAV and self.hasCalendars
 
 
     @inlineCallbacks
     def canAutoSchedule(self, organizer=None):
-        # FIXME:
-        from twistedcaldav.config import config
-
         if config.Scheduling.Options.AutoSchedule.Enabled:
             if (
                 config.Scheduling.Options.AutoSchedule.Always or
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140423/9a5df3ed/attachment-0001.html>


More information about the calendarserver-changes mailing list