[CalendarServer-changes] [13573] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Fri May 30 08:50:39 PDT 2014


Revision: 13573
          http://trac.calendarserver.org//changeset/13573
Author:   cdaboo at apple.com
Date:     2014-05-30 08:50:38 -0700 (Fri, 30 May 2014)
Log Message:
-----------
Make sure group attendees are never sent scheduling messages (but report their schedule status as success). Use a different CUTYPE
for expanded group attendees to avoid clients removing them. Make sure group expansion is on in -test plist.

Modified Paths:
--------------
    CalendarServer/trunk/conf/caldavd-test.plist
    CalendarServer/trunk/twistedcaldav/ical.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/scheduler.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/cuaddress.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
    CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
    CalendarServer/trunk/txdav/caldav/datastore/sql.py
    CalendarServer/trunk/txdav/who/groups.py
    CalendarServer/trunk/txdav/who/test/test_group_attendees.py

Modified: CalendarServer/trunk/conf/caldavd-test.plist
===================================================================
--- CalendarServer/trunk/conf/caldavd-test.plist	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/conf/caldavd-test.plist	2014-05-30 15:50:38 UTC (rev 13573)
@@ -1010,7 +1010,7 @@
 
     <key>GroupAttendees</key>
     <dict>
-      <key>GroupAttendees</key>
+      <key>Enabled</key>
       <true/>
 
       <key>ReconciliationDelaySeconds</key>

Modified: CalendarServer/trunk/twistedcaldav/ical.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/ical.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/twistedcaldav/ical.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -3409,6 +3409,12 @@
 
                 if cutype != prop.parameterValue("CUTYPE"):
                     if cutype and cutype != "INDIVIDUAL":
+                        # For groups we need to change the CUTYPE exposed to clients when we have server-managed
+                        # group attendee expansion because some clients seem to spontaneous remove CUTYPE=GROUP
+                        # for no obvious reason when the event is changed. We can't have that happen for the
+                        # server-managed groups so using a different CUTYPE seems to work around that.
+                        if config.GroupAttendees.Enabled and cutype == "GROUP":
+                            cutype = "X-SERVER-GROUP"
                         prop.setParameter("CUTYPE", cutype)
                     else:
                         prop.removeParameter("CUTYPE")

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/scheduler.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/scheduler.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/caldav/scheduler.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -174,7 +174,7 @@
         if organizer:
             organizerAddress = yield calendarUserFromCalendarUserAddress(organizer, self.txn)
             if organizerAddress.hosted():
-                if organizerAddress.record.calendarsEnabled():
+                if organizerAddress.validOriginator():
 
                     # Only do this check for a freebusy request. A check for an invite needs
                     # to be handled later when we know whether a new invite is being added

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/cuaddress.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/cuaddress.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/cuaddress.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -107,7 +107,15 @@
         return self.record.calendarsEnabled() and not isinstance(self.record, TemporaryDirectoryRecord)
 
 
+    def getCUType(self):
+        """
+        Is this user able to receive scheduling messages.
+        A user with a temporary directory record cannot be scheduled with.
+        """
+        return self.record.getCUType()
 
+
+
 class LocalCalendarUser(HostedCalendarUser):
     """
     User hosted on the current pod.
@@ -181,10 +189,22 @@
     directory entry.
     """
 
+    def __init__(self, cuaddr, record=None):
+        self.cuaddr = cuaddr
+        self.record = record
+
+
     def __str__(self):
         return "Invalid calendar user: {}".format(self.cuaddr)
 
 
+    def hosted(self):
+        """
+        Is this user hosted on this service (this pod or any other)
+        """
+        return self.record is not None
+
+
     def validOriginator(self):
         """
         Is this user able to originate scheduling messages.
@@ -199,7 +219,15 @@
         return False
 
 
+    def getCUType(self):
+        """
+        Is this user able to receive scheduling messages.
+        A user with a temporary directory record cannot be scheduled with.
+        """
+        return self.record.getCUType() if self.record is not None else None
 
+
+
 @inlineCallbacks
 def calendarUserFromCalendarUserAddress(cuaddr, txn):
     """
@@ -284,7 +312,7 @@
     """
     if record is not None:
         if not record.calendarsEnabled():
-            returnValue(InvalidCalendarUser(cuaddr))
+            returnValue(InvalidCalendarUser(cuaddr, record))
         elif record.thisServer():
             returnValue(LocalCalendarUser(cuaddr, record))
         else:

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/implicit.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -1250,12 +1250,17 @@
             if attendee in self.organizerAddress.record.calendarUserAddresses:
                 continue
 
+            attendeeAddress = (yield calendarUserFromCalendarUserAddress(attendee, self.txn))
+
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeeAddress = (yield calendarUserFromCalendarUserAddress(attendee, self.txn))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue
 
+            # Do not schedule with groups - ever
+            if attendeeAddress.hosted() and attendeeAddress.getCUType() == "GROUP":
+                continue
+
             # Generate an iTIP CANCEL message for this attendee, cancelling
             # each instance or the whole
 
@@ -1314,12 +1319,17 @@
             if self.reinvites and attendee not in self.reinvites:
                 continue
 
+            attendeeAddress = (yield calendarUserFromCalendarUserAddress(attendee, self.txn))
+
             # Handle split by not scheduling local attendees
             if self.split_details is not None:
-                attendeeAddress = (yield calendarUserFromCalendarUserAddress(attendee, self.txn))
                 if type(attendeeAddress) is LocalCalendarUser:
                     continue
 
+            # Do not schedule with groups - ever
+            if attendeeAddress.hosted() and attendeeAddress.getCUType() == "GROUP":
+                continue
+
             itipmsg = iTipGenerator.generateAttendeeRequest(self.calendar, (attendee,), self.changed_rids)
 
             # Send scheduling message

Modified: CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/caldav/datastore/scheduling/ischedule/scheduler.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -27,7 +27,8 @@
 
 from txdav.caldav.datastore.scheduling import addressmapping
 from txdav.caldav.datastore.scheduling.cuaddress import LocalCalendarUser, \
-    calendarUserFromCalendarUserAddress, RemoteCalendarUser
+    calendarUserFromCalendarUserAddress, RemoteCalendarUser, \
+    OtherServerCalendarUser
 from txdav.caldav.datastore.scheduling.ischedule import xml
 from txdav.caldav.datastore.scheduling.ischedule.dkim import DKIMVerifier, \
     DKIMVerificationError, DKIMMissingError
@@ -257,9 +258,19 @@
                     self.errorElements["originator-denied"],
                     "Originator cannot be local to server",
                 ))
-            else:
+            elif isinstance(originatorAddress, OtherServerCalendarUser):
                 self.originator = originatorAddress
                 self._validAlternateServer(originatorAddress)
+            else:
+                log.error(
+                    "Cannot use invalid originator: {o}",
+                    o=self.originator,
+                )
+                raise HTTPError(self.errorResponse(
+                    responsecode.FORBIDDEN,
+                    self.errorElements["originator-denied"],
+                    "Originator cannot schedule",
+                ))
         else:
             if self._podding:
                 log.error(
@@ -436,10 +447,20 @@
                         self.errorElements["organizer-denied"],
                         "Organizer is not local to server",
                     ))
-                else:
+                elif isinstance(organizerAddress, OtherServerCalendarUser):
                     # Check that the origin server is the correct pod
                     self.organizer = organizerAddress
                     self._validAlternateServer(self.organizer)
+                else:
+                    log.error(
+                        "Invalid ORGANIZER in calendar data: {cal}",
+                        cal=self.calendar,
+                    )
+                    raise HTTPError(self.errorResponse(
+                        responsecode.FORBIDDEN,
+                        self.errorElements["organizer-denied"],
+                        "Organizer cannot schedule",
+                    ))
             else:
                 localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(organizer))
                 if localUser:
@@ -486,8 +507,18 @@
                     self.errorElements["attendee-denied"],
                     "Local attendee cannot send to this server",
                 ))
+            elif isinstance(attendeeAddress, OtherServerCalendarUser):
+                self._validAlternateServer(attendeeAddress)
             else:
-                self._validAlternateServer(attendeeAddress)
+                log.error(
+                    "Invalid ATTENDEE in calendar data: {cal}",
+                    cal=self.calendar,
+                )
+                raise HTTPError(self.errorResponse(
+                    responsecode.FORBIDDEN,
+                    self.errorElements["attendee-denied"],
+                    "Attendee not allowed to schedule",
+                ))
         else:
             localUser = (yield addressmapping.mapper.isCalendarUserInMyDomain(self.attendee))
             if localUser:

Modified: CalendarServer/trunk/txdav/caldav/datastore/sql.py
===================================================================
--- CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/caldav/datastore/sql.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -1940,10 +1940,13 @@
         if not config.GroupAttendees.Enabled:
             returnValue(False)
 
+        # Note, as per ical.py/normalizeCalendarUserAddresses we change the CUTYPE from GROUP
+        # to X-SERVER-GROUP to ensure bad clients don't spontaneously remove group attendees
+        # when an event changes.
         attendeeProps = component.getAllAttendeeProperties()
         groupCUAs = set([
             attendeeProp.value() for attendeeProp in attendeeProps
-            if attendeeProp.parameterValue("CUTYPE") == "GROUP"
+            if attendeeProp.parameterValue("CUTYPE") == "X-SERVER-GROUP"
         ])
 
         groupCUAToAttendeeMemberPropMap = {}

Modified: CalendarServer/trunk/txdav/who/groups.py
===================================================================
--- CalendarServer/trunk/txdav/who/groups.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/who/groups.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -291,7 +291,7 @@
 
             membershipHashContent = hashlib.md5()
             members = list(members)
-            members.sort(cmp=lambda x, y: cmp(x.uid, y.uid))
+            members.sort(key=lambda x: x.uid)
             for member in members:
                 membershipHashContent.update(str(member.uid))
             membershipHash = membershipHashContent.hexdigest()
@@ -397,7 +397,6 @@
         rows = yield Select(
             [gr.GROUP_UID],
             From=gr,
-            Distinct=True,
             Where=gr.GROUP_ID.In(
                 Select(
                     [groupAttendee.GROUP_ID],
@@ -407,6 +406,9 @@
             )
         ).on(txn)
         attendeeGroupUIDs = set([row[0] for row in rows])
+        self.log.info(
+            "There are {count} group attendees", count=len(attendeeGroupUIDs)
+        )
 
         # FIXME: is this a good place to clear out unreferenced groups?
 

Modified: CalendarServer/trunk/txdav/who/test/test_group_attendees.py
===================================================================
--- CalendarServer/trunk/txdav/who/test/test_group_attendees.py	2014-05-30 02:37:00 UTC (rev 13572)
+++ CalendarServer/trunk/txdav/who/test/test_group_attendees.py	2014-05-30 15:50:38 UTC (rev 13573)
@@ -139,7 +139,7 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -168,7 +168,7 @@
     @inlineCallbacks
     def test_unknownPUT(self):
         """
-        Test unknown group with CUTYPE=GROUP handled
+        Test unknown group with CUTYPE=X-SERVER-GROUP handled
         """
         calendar = yield self.calendarUnderTest(name="calendar", home="10000000-0000-0000-0000-000000000001")
 
@@ -185,7 +185,7 @@
 UID:event1 at ninevah.local
 ORGANIZER:MAILTO:user01 at example.com
 ATTENDEE:mailto:user01 at example.com
-ATTENDEE;CUTYPE=GROUP:urn:uuid:FFFFFFFF-EEEE-DDDD-CCCC-BBBBBBBBBBBB
+ATTENDEE;CUTYPE=X-SERVER-GROUP:urn:uuid:FFFFFFFF-EEEE-DDDD-CCCC-BBBBBBBBBBBB
 END:VEVENT
 END:VCALENDAR"""
 
@@ -198,7 +198,7 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CUTYPE=GROUP;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:uuid:FFFFFFFF-EEEE-DDDD-CCCC-BBBBBBBBBBBB
+ATTENDEE;CUTYPE=X-SERVER-GROUP;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:uuid:FFFFFFFF-EEEE-DDDD-CCCC-BBBBBBBBBBBB
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000001
 SUMMARY:event 1
@@ -250,7 +250,7 @@
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 01;EMAIL=user01 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000001
 SUMMARY:event 1
@@ -305,7 +305,7 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 04;CUTYPE=GROUP;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000004
+ATTENDEE;CN=Group 04;CUTYPE=X-SERVER-GROUP;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000004
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000004";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000004";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000004";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -373,9 +373,9 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 03;CUTYPE=GROUP;EMAIL=group03 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000003
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000003
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -432,7 +432,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
 SUMMARY:event 1
@@ -449,7 +449,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
@@ -468,7 +468,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
 SEQUENCE:2
@@ -488,7 +488,7 @@
         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 0)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         self.assertEqual(len(wps), 0)
 
@@ -505,10 +505,10 @@
         yield self.commit()
 
         self.patch(CalendarDirectoryRecordMixin, "expandedMembers", unpatchedExpandedMembers)
-        
+
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 1)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
         self.assertEqual(len(wps), 1)
@@ -570,7 +570,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 0{0};EMAIL=user0{0}@example.com:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
 SUMMARY:event {0}
@@ -587,7 +587,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 0{0};EMAIL=user0{0}@example.com:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
@@ -606,7 +606,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 0{0};EMAIL=user0{0}@example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 0{0};EMAIL=user0{0}@example.com:urn:x-uid:10000000-0000-0000-0000-00000000000{0}
 SEQUENCE:2
@@ -626,7 +626,7 @@
         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 0)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         self.assertEqual(len(wps), 0)
 
@@ -649,7 +649,7 @@
 
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 1)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
         self.assertEqual(len(wps), len(userRange))
@@ -714,7 +714,7 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
@@ -731,7 +731,7 @@
 
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 0)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         if len(wps): # This is needed because the test currently fails and does actually create job items we have to wait for
             yield self.commit()
@@ -792,7 +792,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
@@ -810,7 +810,7 @@
 DTSTART;TZID=US/Eastern:20140101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
 SEQUENCE:1
@@ -833,7 +833,7 @@
 
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 1)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
         self.assertEqual(len(wps), 1)
@@ -923,7 +923,7 @@
 DTSTART;TZID=US/Eastern:20120101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000001";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
@@ -942,7 +942,7 @@
 DTSTART;TZID=US/Eastern:20120101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 02;EMAIL=user02 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
 CREATED:20060101T150000Z
 ORGANIZER;CN=User 02;EMAIL=user02 at example.com:urn:x-uid:10000000-0000-0000-0000-000000000002
 RRULE:FREQ=DAILY;UNTIL=20140101T100000
@@ -966,7 +966,7 @@
 
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 1)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000001")
         yield self.commit()
         self.assertEqual(len(wps), 1)
@@ -995,7 +995,6 @@
         self.assertEqual(len(wps), 0)
 
 
-
     @inlineCallbacks
     def test_groupRemovalFromDirectory(self):
         """
@@ -1040,9 +1039,9 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 03;CUTYPE=GROUP;EMAIL=group03 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000003
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000003
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -1062,9 +1061,9 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 03;CUTYPE=GROUP;EMAIL=group03 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000003
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000003
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
 ATTENDEE;CN=User 09;EMAIL=user09 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000009
@@ -1085,9 +1084,9 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 01;CUTYPE=GROUP;EMAIL=group01 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 03;CUTYPE=GROUP;EMAIL=group03 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000003
+ATTENDEE;CN=Group 01;CUTYPE=X-SERVER-GROUP;EMAIL=group01 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000001
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000003
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -1127,7 +1126,7 @@
         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 3)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000002")
         yield self.commit()
         self.assertEqual(len(wps), 1)
@@ -1219,8 +1218,8 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
-ATTENDEE;CN=Group 03;CUTYPE=GROUP;EMAIL=group03 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000003
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 03;CUTYPE=X-SERVER-GROUP;EMAIL=group03 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000003
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002","urn:x-uid:20000000-0000-0000-0000-000000000003";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -1257,7 +1256,7 @@
 DTSTART;TZID=US/Eastern:20240101T100000
 DURATION:PT1H
 ATTENDEE;CN=User 01;EMAIL=user01 at example.com;RSVP=TRUE:urn:x-uid:10000000-0000-0000-0000-000000000001
-ATTENDEE;CN=Group 02;CUTYPE=GROUP;EMAIL=group02 at example.com;RSVP=TRUE;SCHEDULE-STATUS=3.7:urn:x-uid:20000000-0000-0000-0000-000000000002
+ATTENDEE;CN=Group 02;CUTYPE=X-SERVER-GROUP;EMAIL=group02 at example.com;RSVP=TRUE:urn:x-uid:20000000-0000-0000-0000-000000000002
 ATTENDEE;CN=User 06;EMAIL=user06 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000006
 ATTENDEE;CN=User 07;EMAIL=user07 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000007
 ATTENDEE;CN=User 08;EMAIL=user08 at example.com;MEMBER="urn:x-uid:20000000-0000-0000-0000-000000000002";PARTSTAT=NEEDS-ACTION;RSVP=TRUE;SCHEDULE-STATUS=1.2:urn:x-uid:10000000-0000-0000-0000-000000000008
@@ -1286,7 +1285,7 @@
         groupCacher = GroupCacher(self.transactionUnderTest().directoryService())
         groupsToRefresh = yield groupCacher.groupsToRefresh(self.transactionUnderTest())
         self.assertEqual(len(groupsToRefresh), 2)
-        
+
         wps = yield groupCacher.refreshGroup(self.transactionUnderTest(), "20000000-0000-0000-0000-000000000002")
         yield self.commit()
         self.assertEqual(len(wps), 1)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140530/f01fd8dd/attachment-0001.html>


More information about the calendarserver-changes mailing list