[CalendarServer-changes] [10008] CalendarServer/trunk

source_changes at macosforge.org source_changes at macosforge.org
Thu Nov 8 15:22:14 PST 2012


Revision: 10008
          http://trac.calendarserver.org//changeset/10008
Author:   sagen at apple.com
Date:     2012-11-08 15:22:14 -0800 (Thu, 08 Nov 2012)
Log Message:
-----------
Auto-accept group feature added.  You can now assign an auto-accept group to a principal, and that principal will then automatically accept invites from members of that group (or automatically decline if there is a conflict).

Modified Paths:
--------------
    CalendarServer/trunk/calendarserver/tools/gateway.py
    CalendarServer/trunk/calendarserver/tools/principals.py
    CalendarServer/trunk/calendarserver/tools/test/gateway/users-groups.xml
    CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
    CalendarServer/trunk/doc/calendarserver_manage_principals.8
    CalendarServer/trunk/twistedcaldav/directory/aggregate.py
    CalendarServer/trunk/twistedcaldav/directory/augment.py
    CalendarServer/trunk/twistedcaldav/directory/directory.py
    CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
    CalendarServer/trunk/twistedcaldav/directory/principal.py
    CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
    CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
    CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
    CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py
    CalendarServer/trunk/twistedcaldav/scheduling/processing.py

Modified: CalendarServer/trunk/calendarserver/tools/gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/gateway.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/calendarserver/tools/gateway.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -171,6 +171,7 @@
     'Country' : { 'extras' : True, 'attr' : 'country', },
     'Phone' : { 'extras' : True, 'attr' : 'phone', },
     'AutoSchedule' : { 'attr' : 'autoSchedule', },
+    'AutoAcceptGroup' : { 'attr' : 'autoAcceptGroup', },
 }
 
 class Runner(object):
@@ -249,6 +250,7 @@
             respondWithError("Principal not found: %s" % (guid,))
             return
         recordDict['AutoSchedule'] = principal.getAutoSchedule()
+        recordDict['AutoAcceptGroup'] = principal.getAutoAcceptGroup()
         recordDict['ReadProxies'], recordDict['WriteProxies'] = (yield getProxies(principal))
         respond(command, recordDict)
 
@@ -262,6 +264,7 @@
         principal = principalForPrincipalID(command['GeneratedUID'],
             directory=self.dir)
         (yield principal.setAutoSchedule(command.get('AutoSchedule', False)))
+        (yield principal.setAutoAcceptGroup(command.get('AutoAcceptGroup', "")))
 
         kwargs = {}
         for key, info in attrMap.iteritems():
@@ -325,6 +328,7 @@
         principal = principalForPrincipalID(command['GeneratedUID'],
             directory=self.dir)
         (yield principal.setAutoSchedule(command.get('AutoSchedule', False)))
+        (yield principal.setAutoAcceptGroup(command.get('AutoAcceptGroup', "")))
 
         kwargs = {}
         for key, info in attrMap.iteritems():

Modified: CalendarServer/trunk/calendarserver/tools/principals.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/principals.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/calendarserver/tools/principals.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -88,6 +88,8 @@
     print "  --get-auto-schedule: read auto-schedule state"
     print "  --set-auto-schedule-mode={default|none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic}: set auto-schedule mode"
     print "  --get-auto-schedule-mode: read auto-schedule mode"
+    print "  --set-auto-accept-group=principal: set auto-accept-group"
+    print "  --get-auto-accept-group: read auto-accept-group"
     print "  --add {locations|resources} 'full name' [record name] [GUID]: add a principal"
     print "  --remove: remove a principal"
 
@@ -118,6 +120,8 @@
                 "get-auto-schedule",
                 "set-auto-schedule-mode=",
                 "get-auto-schedule-mode",
+                "set-auto-accept-group=",
+                "get-auto-accept-group",
                 "verbose",
             ],
         )
@@ -223,6 +227,18 @@
         elif opt in ("", "--get-auto-schedule-mode"):
             principalActions.append((action_getAutoScheduleMode,))
 
+        elif opt in ("", "--set-auto-accept-group"):
+            try:
+                principalForPrincipalID(arg, checkOnly=True)
+            except ValueError, e:
+                abort(e)
+
+            principalActions.append((action_setAutoAcceptGroup, arg))
+
+        elif opt in ("", "--get-auto-accept-group"):
+            principalActions.append((action_getAutoAcceptGroup,))
+
+
         else:
             raise NotImplementedError(opt)
 
@@ -768,7 +784,50 @@
         autoScheduleMode,
     )
 
+ at inlineCallbacks
+def action_setAutoAcceptGroup(principal, autoAcceptGroup):
+    if principal.record.recordType == "groups":
+        print "Setting auto-accept-group for %s is not allowed." % (principal,)
 
+    elif principal.record.recordType == "users" and not config.Scheduling.Options.AutoSchedule.AllowUsers:
+        print "Setting auto-accept-group for %s is not allowed." % (principal,)
+
+    else:
+        groupPrincipal = principalForPrincipalID(autoAcceptGroup)
+        if groupPrincipal is None or groupPrincipal.record.recordType != "groups":
+            print "Invalid principal ID: %s" % (autoAcceptGroup,)
+        else:
+            print "Setting auto-accept-group to %s for %s" % (
+                prettyPrincipal(groupPrincipal),
+                prettyPrincipal(principal),
+            )
+
+            (yield updateRecord(False, config.directory,
+                principal.record.recordType,
+                guid=principal.record.guid,
+                shortNames=principal.record.shortNames,
+                fullName=principal.record.fullName,
+                autoAcceptGroup=groupPrincipal.record.guid,
+                **principal.record.extras
+            ))
+
+def action_getAutoAcceptGroup(principal):
+    autoAcceptGroup = principal.getAutoAcceptGroup()
+    if autoAcceptGroup:
+        record = config.directory.recordWithGUID(autoAcceptGroup)
+        if record is not None:
+            groupPrincipal = config.directory.principalCollection.principalForUID(record.uid)
+            if groupPrincipal is not None:
+                print "Auto-accept-group for %s is %s" % (
+                    prettyPrincipal(principal),
+                    prettyPrincipal(groupPrincipal),
+                )
+                return
+        print "Invalid auto-accept-group assigned: %s" % (autoAcceptGroup,)
+    else:
+        print "No auto-accept-group assigned to %s" % (prettyPrincipal(principal),)
+
+
 def abort(msg, status=1):
     sys.stdout.write("%s\n" % (msg,))
     try:
@@ -856,18 +915,33 @@
     matching the guid in kwargs.
     """
 
+    assignAutoSchedule = False
     if kwargs.has_key("autoSchedule"):
+        assignAutoSchedule = True
         autoSchedule = kwargs["autoSchedule"]
         del kwargs["autoSchedule"]
-    else:
+    elif create:
+        assignAutoSchedule = True
         autoSchedule = recordType in ("locations", "resources")
 
+    assignAutoScheduleMode = False
     if kwargs.has_key("autoScheduleMode"):
+        assignAutoScheduleMode = True
         autoScheduleMode = kwargs["autoScheduleMode"]
         del kwargs["autoScheduleMode"]
-    else:
+    elif create:
+        assignAutoScheduleMode = True
         autoScheduleMode = None
 
+    assignAutoAcceptGroup = False
+    if kwargs.has_key("autoAcceptGroup"):
+        assignAutoAcceptGroup = True
+        autoAcceptGroup = kwargs["autoAcceptGroup"]
+        del kwargs["autoAcceptGroup"]
+    elif create:
+        assignAutoAcceptGroup = True
+        autoAcceptGroup = None
+
     for key, value in kwargs.items():
         if isinstance(value, unicode):
             kwargs[key] = value.encode("utf-8")
@@ -890,8 +964,13 @@
 
     augmentService = directory.serviceForRecordType(recordType).augmentService
     augmentRecord = (yield augmentService.getAugmentRecord(kwargs['guid'], recordType))
-    augmentRecord.autoSchedule = autoSchedule
-    augmentRecord.autoScheduleMode = autoScheduleMode
+
+    if assignAutoSchedule:
+        augmentRecord.autoSchedule = autoSchedule
+    if assignAutoScheduleMode:
+        augmentRecord.autoScheduleMode = autoScheduleMode
+    if assignAutoAcceptGroup:
+        augmentRecord.autoAcceptGroup = autoAcceptGroup
     (yield augmentService.addAugmentRecords([augmentRecord]))
     try:
         directory.updateRecord(recordType, **kwargs)

Modified: CalendarServer/trunk/calendarserver/tools/test/gateway/users-groups.xml
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/gateway/users-groups.xml	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/calendarserver/tools/test/gateway/users-groups.xml	2012-11-08 23:22:14 UTC (rev 10008)
@@ -37,4 +37,13 @@
       <member type="users">user02</member>
     </members>
   </group>
+  <group>
+    <uid>testgroup2</uid>
+    <guid>f5a6142c-4189-4e9e-90b0-9cd0268b314b</guid>
+    <password>test</password>
+    <name>Group 02</name>
+    <members>
+      <member type="users">user01</member>
+    </members>
+  </group>
 </accounts>

Modified: CalendarServer/trunk/calendarserver/tools/test/test_gateway.py
===================================================================
--- CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/calendarserver/tools/test/test_gateway.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -121,6 +121,7 @@
         self.assertEquals(results["result"]["RealName"], "Created Location 01 %s" % unichr(208))
         self.assertEquals(results["result"]["Comment"], "Test Comment")
         self.assertEquals(results["result"]["AutoSchedule"], True)
+        self.assertEquals(results["result"]["AutoAcceptGroup"], "E5A6142C-4189-4E9E-90B0-9CD0268B314B")
         self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03', 'user04']))
         self.assertEquals(set(results["result"]["WriteProxies"]), set(['user05', 'user06']))
 
@@ -202,9 +203,11 @@
         self.assertEquals(record.extras["country"], "Updated USA")
         self.assertEquals(record.extras["phone"], "(408) 555-1213")
         self.assertEquals(record.autoSchedule, True)
+        self.assertEquals(record.autoAcceptGroup, "F5A6142C-4189-4E9E-90B0-9CD0268B314B")
 
         results = yield self.runCommand(command_getLocationAttributes)
         self.assertEquals(results["result"]["AutoSchedule"], True)
+        self.assertEquals(results["result"]["AutoAcceptGroup"], "F5A6142C-4189-4E9E-90B0-9CD0268B314B")
         self.assertEquals(set(results["result"]["ReadProxies"]), set(['user03']))
         self.assertEquals(set(results["result"]["WriteProxies"]), set(['user05', 'user06', 'user07']))
 
@@ -312,6 +315,8 @@
         <string>createLocation</string>
         <key>AutoSchedule</key>
         <true/>
+        <key>AutoAcceptGroup</key>
+        <string>E5A6142C-4189-4E9E-90B0-9CD0268B314B</string>
         <key>GeneratedUID</key>
         <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
         <key>RealName</key>
@@ -495,6 +500,8 @@
         <string>setLocationAttributes</string>
         <key>AutoSchedule</key>
         <true/>
+        <key>AutoAcceptGroup</key>
+        <string>F5A6142C-4189-4E9E-90B0-9CD0268B314B</string>
         <key>GeneratedUID</key>
         <string>836B1B66-2E9A-4F46-8B1C-3DD6772C20B2</string>
         <key>RealName</key>

Modified: CalendarServer/trunk/doc/calendarserver_manage_principals.8
===================================================================
--- CalendarServer/trunk/doc/calendarserver_manage_principals.8	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/doc/calendarserver_manage_principals.8	2012-11-08 23:22:14 UTC (rev 10008)
@@ -38,6 +38,8 @@
 .Op Fl -get-auto-schedule
 .Op Fl -set-auto-schedule-mode Ar none|accept-always|decline-always|accept-if-free|decline-if-busy|automatic
 .Op Fl -get-auto-schedule-mode
+.Op Fl -set-auto-accept-group Ar group
+.Op Fl -get-auto-accept-group
 .Op Fl -add Ar locations|resources full-name [record-name] [GUID]
 .Op Fl -remove
 .Ar principal
@@ -123,6 +125,11 @@
 Enable or disable automatic scheduling.
 .It Fl -get-auto-schedule
 Get the automatic scheduling state.
+.It Fl -set-auto-accept-group Ar group
+The principal will auto-accept any invites from any member of the group (as long
+as there are no conflicts).
+.It Fl -get-auto-accept-group
+Get the currently assigned auto-accept group for the principal.
 .It Fl -add Ar locations|resources full-name [record-name] [GUID]
 Add a new location or resource. Record name and GUID are optional.  If
 GUID is not specified, one will be generated.  If record name is not

Modified: CalendarServer/trunk/twistedcaldav/directory/aggregate.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/aggregate.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/aggregate.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -67,6 +67,8 @@
                     )
                 recordTypes[recordType] = service
 
+            service.aggregateService = self
+
         self.realmName = realmName
         self._recordTypes = recordTypes
 

Modified: CalendarServer/trunk/twistedcaldav/directory/augment.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/augment.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/augment.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -60,6 +60,7 @@
         enabledForCalendaring=False,
         autoSchedule=False,
         autoScheduleMode="default",
+        autoAcceptGroup="",
         enabledForAddressBooks=False,
         enabledForLogin=True,
     ):
@@ -72,6 +73,7 @@
         self.enabledForLogin = enabledForLogin
         self.autoSchedule = autoSchedule
         self.autoScheduleMode = autoScheduleMode if autoScheduleMode in allowedAutoScheduleModes else "default"
+        self.autoAcceptGroup = autoAcceptGroup
         self.clonedFromDefault = False
 
 recordTypesMap = {
@@ -459,6 +461,8 @@
         addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE, "true" if record.autoSchedule else "false")
         if record.autoScheduleMode:
             addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOSCHEDULE_MODE, record.autoScheduleMode)
+        if record.autoAcceptGroup:
+            addSubElement(recordNode, xmlaugmentsparser.ELEMENT_AUTOACCEPTGROUP, record.autoAcceptGroup)
 
     def refresh(self):
         """
@@ -570,11 +574,11 @@
         """
         
         # Query for the record information
-        results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED from AUGMENTS where UID = :1", (uid,)))
+        results = (yield self.query("select UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, AUTOACCEPTGROUP, LOGINENABLED from AUGMENTS where UID = :1", (uid,)))
         if not results:
             returnValue(None)
         else:
-            uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule, autoScheduleMode, enabledForLogin = results[0]
+            uid, enabled, serverid, partitionid, enabledForCalendaring, enabledForAddressBooks, autoSchedule, autoScheduleMode, autoAcceptGroup, enabledForLogin = results[0]
             
             record = AugmentRecord(
                 uid = uid,
@@ -586,6 +590,7 @@
                 enabledForLogin = enabledForLogin == "T",
                 autoSchedule = autoSchedule == "T",
                 autoScheduleMode = autoScheduleMode,
+                autoAcceptGroup = autoAcceptGroup,
             )
             
             returnValue(record)
@@ -648,6 +653,7 @@
                 ("ADDRESSBOOKS",     "text(1)"),
                 ("AUTOSCHEDULE",     "text(1)"),
                 ("AUTOSCHEDULEMODE", "text"),
+                ("AUTOACCEPTGROUP",  "text"),
                 ("LOGINENABLED",     "text(1)"),
             ),
             ifnotexists=True,
@@ -671,8 +677,8 @@
     def _addRecord(self, record):
         yield self.execute(
             """insert or replace into AUGMENTS
-            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED)
-            values (:1, :2, :3, :4, :5, :6, :7, :8, :9)""",
+            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, AUTOACCEPTGROUP, LOGINENABLED)
+            values (:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)""",
             (
                 record.uid,
                 "T" if record.enabled else "F",
@@ -682,6 +688,7 @@
                 "T" if record.enabledForAddressBooks else "F",
                 "T" if record.autoSchedule else "F",
                 record.autoScheduleMode if record.autoScheduleMode else "",
+                record.autoAcceptGroup,
                 "T" if record.enabledForLogin else "F",
             )
         )
@@ -703,8 +710,8 @@
     def _addRecord(self, record):
         yield self.execute(
             """insert into AUGMENTS
-            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED)
-            values (:1, :2, :3, :4, :5, :6, :7, :8, :9)""",
+            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, AUTOACCEPTGROUP, LOGINENABLED)
+            values (:1, :2, :3, :4, :5, :6, :7, :8, :9, :10)""",
             (
                 record.uid,
                 "T" if record.enabled else "F",
@@ -714,6 +721,7 @@
                 "T" if record.enabledForAddressBooks else "F",
                 "T" if record.autoSchedule else "F",
                 record.autoScheduleMode if record.autoScheduleMode else "",
+                record.autoAcceptGroup,
                 "T" if record.enabledForLogin else "F",
             )
         )
@@ -722,8 +730,8 @@
     def _modifyRecord(self, record):
         yield self.execute(
             """update AUGMENTS set
-            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, LOGINENABLED) =
-            (:1, :2, :3, :4, :5, :6, :7, :8, :9) where UID = :10""",
+            (UID, ENABLED, SERVERID, PARTITIONID, CALENDARING, ADDRESSBOOKS, AUTOSCHEDULE, AUTOSCHEDULEMODE, AUTOACCEPTGROUP, LOGINENABLED) =
+            (:1, :2, :3, :4, :5, :6, :7, :8, :9, :10) where UID = :11""",
             (
                 record.uid,
                 "T" if record.enabled else "F",
@@ -733,6 +741,7 @@
                 "T" if record.enabledForAddressBooks else "F",
                 "T" if record.autoSchedule else "F",
                 record.autoScheduleMode if record.autoScheduleMode else "",
+                record.autoAcceptGroup,
                 "T" if record.enabledForLogin else "F",
                 record.uid,
             )

Modified: CalendarServer/trunk/twistedcaldav/directory/directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/directory.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/directory.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -82,6 +82,8 @@
     searchContext_location = "location"
     searchContext_attendee = "attendee"
 
+    aggregateService = None
+
     def _generatedGUID(self):
         if not hasattr(self, "_guid"):
             realmName = self.realmName
@@ -477,6 +479,7 @@
             autoaccept = wpframework.get("AutoAcceptsInvitation", False)
             proxy = wpframework.get("CalendaringDelegate", None)
             read_only_proxy = wpframework.get("ReadOnlyCalendaringDelegate", None)
+            autoAcceptGroup = wpframework.get("AutoAcceptGroup", "")
         except (ExpatError, AttributeError), e:
             self.log_error(
                 "Failed to parse ResourceInfo attribute of record (%s)%s (guid=%s): %s\n%s" %
@@ -484,7 +487,7 @@
             )
             raise ValueError("Invalid ResourceInfo")
 
-        return (autoaccept, proxy, read_only_proxy,)
+        return (autoaccept, proxy, read_only_proxy, autoAcceptGroup)
 
 
     def getExternalProxyAssignments(self):
@@ -1245,6 +1248,7 @@
         firstName=None, lastName=None, emailAddresses=set(),
         calendarUserAddresses=set(),
         autoSchedule=False, autoScheduleMode=None,
+        autoAcceptGroup="",
         enabledForCalendaring=None,
         enabledForAddressBooks=None,
         uid=None,
@@ -1280,6 +1284,7 @@
         self.enabledForCalendaring = enabledForCalendaring
         self.autoSchedule = autoSchedule
         self.autoScheduleMode = autoScheduleMode
+        self.autoAcceptGroup = autoAcceptGroup
         self.enabledForAddressBooks = enabledForAddressBooks
         self.enabledForLogin = enabledForLogin
         self.extProxies = extProxies
@@ -1353,6 +1358,7 @@
             self.enabledForAddressBooks = augment.enabledForAddressBooks
             self.autoSchedule = augment.autoSchedule
             self.autoScheduleMode = augment.autoScheduleMode
+            self.autoAcceptGroup = augment.autoAcceptGroup
             self.enabledForLogin = augment.enabledForLogin
 
             if (self.enabledForCalendaring or self.enabledForAddressBooks) and self.recordType == self.service.recordType_groups:
@@ -1556,7 +1562,28 @@
         return True
 
 
+    def autoAcceptMembers(self):
+        """
+        Return the list of GUIDs for which this record will automatically accept
+        invites from (assuming no conflicts).  This list is based on the group
+        assigned to record.autoAcceptGroup.  Cache the expanded group membership
+        within the record.
 
+        @return: the list of members of the autoAcceptGroup, or an empty list if
+            not assigned
+        @rtype: C{list} of GUID C{str}
+        """
+        if not hasattr(self, "_cachedAutoAcceptMembers"):
+            self._cachedAutoAcceptMembers = []
+            if self.autoAcceptGroup:
+                service = self.service.aggregateService or self.service
+                groupRecord = service.recordWithGUID(self.autoAcceptGroup)
+                if groupRecord is not None:
+                    self._cachedAutoAcceptMembers = [m.guid for m in groupRecord.expandedMembers()]
+
+        return self._cachedAutoAcceptMembers
+
+
 class DirectoryError(RuntimeError):
     """
     Generic directory error.

Modified: CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/ldapdirectory.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -182,6 +182,7 @@
                 "autoScheduleEnabledValue": "yes",
                 "proxyAttr": None, # list of GUIDs
                 "readOnlyProxyAttr": None, # list of GUIDs
+                "autoAcceptGroupAttr": None, # single group GUID
             },
             "partitionSchema": {
                 "serverIdAttr": None, # maps to augments server-id
@@ -261,6 +262,8 @@
             attrSet.add(self.resourceSchema["resourceInfoAttr"])
         if self.resourceSchema["autoScheduleAttr"]:
             attrSet.add(self.resourceSchema["autoScheduleAttr"])
+        if self.resourceSchema["autoAcceptGroupAttr"]:
+            attrSet.add(self.resourceSchema["autoAcceptGroupAttr"])
         if self.resourceSchema["proxyAttr"]:
             attrSet.add(self.resourceSchema["proxyAttr"])
         if self.resourceSchema["readOnlyProxyAttr"]:
@@ -787,6 +790,7 @@
         proxyGUIDs = ()
         readOnlyProxyGUIDs = ()
         autoSchedule = False
+        autoAcceptGroup = ""
         memberGUIDs = []
 
         # LDAP attribute -> principal matchings
@@ -836,7 +840,8 @@
                         (
                             autoSchedule,
                             proxy,
-                            readOnlyProxy
+                            readOnlyProxy,
+                            autoAcceptGroup
                         ) = self.parseResourceInfo(
                             resourceInfo,
                             guid,
@@ -861,6 +866,9 @@
                 if self.resourceSchema["readOnlyProxyAttr"]:
                     readOnlyProxyGUIDs = set(self._getMultipleLdapAttributes(attrs,
                         self.resourceSchema["readOnlyProxyAttr"]))
+                if self.resourceSchema["autoAcceptGroupAttr"]:
+                    autoAcceptGroup = self._getUniqueLdapAttribute(attrs,
+                        self.resourceSchema["autoAcceptGroupAttr"])
 
         serverID = partitionID = None
         if self.partitionSchema["serverIdAttr"]:
@@ -906,6 +914,7 @@
                 partitionID=partitionID,
                 enabledForCalendaring=enabledForCalendaring,
                 autoSchedule=autoSchedule,
+                autoAcceptGroup=autoAcceptGroup,
                 enabledForAddressBooks=enabledForAddressBooks, # TODO: add to LDAP?
                 enabledForLogin=enabledForLogin,
             )

Modified: CalendarServer/trunk/twistedcaldav/directory/principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/principal.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/principal.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -994,14 +994,20 @@
     def getAutoSchedule(self):
         return self.record.autoSchedule
 
-    def canAutoSchedule(self):
+    def canAutoSchedule(self, organizer=None):
         """
         Determine the auto-schedule state based on record state, type and config settings.
+
+        @param organizer: the CUA of the organizer trying to schedule this principal
+        @type organizer: C{str}
         """
-        
+
         if config.Scheduling.Options.AutoSchedule.Enabled:
-            if config.Scheduling.Options.AutoSchedule.Always or self.getAutoSchedule():
-                if self.getCUType() != "INDIVIDUAL" or config.Scheduling.Options.AutoSchedule.AllowUsers:
+            if (config.Scheduling.Options.AutoSchedule.Always or
+                self.getAutoSchedule() or
+                self.autoAcceptFromOrganizer(organizer)):
+                if (self.getCUType() != "INDIVIDUAL" or
+                    config.Scheduling.Options.AutoSchedule.AllowUsers):
                     return True
         return False
 
@@ -1012,9 +1018,65 @@
         augmentRecord.autoScheduleMode = autoScheduleMode
         (yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
 
-    def getAutoScheduleMode(self):
-        return self.record.autoScheduleMode
+    def getAutoScheduleMode(self, organizer=None):
+        """
+        Return the auto schedule mode value for the principal.  If the optional
+        organizer is provided, and that organizer is a member of the principal's
+        auto-accept group, return "automatic" instead; this allows specifying a
+        priliveged group whose scheduling requests are automatically accepted or
+        declined, regardless of whether the principal is normally managed by a
+        delegate.
 
+        @param organizer: the CUA of the organizer scheduling this principal
+        @type organizer: C{str}
+        @return: auto schedule mode; one of: none, accept-always, decline-always,
+            accept-if-free, decline-if-busy, automatic (see stdconfig.py)
+        @rtype: C{str}
+        """
+        autoScheduleMode = self.record.autoScheduleMode
+        if self.autoAcceptFromOrganizer(organizer):
+            autoScheduleMode = "automatic"
+        return autoScheduleMode
+
+
+    @inlineCallbacks
+    def setAutoAcceptGroup(self, autoAcceptGroup):
+        """
+        Sets the group whose members can automatically schedule with this principal
+        even if this principal's auto-schedule is False (assuming no conflicts).
+
+        @param autoAcceptGroup:  GUID of the group
+        @type autoAcceptGroup: C{str}
+        """
+        self.record.autoAcceptGroup = autoAcceptGroup
+        augmentRecord = (yield self.record.service.augmentService.getAugmentRecord(self.record.guid, self.record.recordType))
+        augmentRecord.autoAcceptGroup = autoAcceptGroup
+        (yield self.record.service.augmentService.addAugmentRecords([augmentRecord]))
+
+    def getAutoAcceptGroup(self):
+        """
+        Returns the GUID of the auto accept group assigned to this principal, or empty
+        string if not assigned
+        """
+        return self.record.autoAcceptGroup
+
+    def autoAcceptFromOrganizer(self, organizer):
+        """
+        Is the organizer a member of this principal's autoAcceptGroup?
+
+        @param organizer: CUA of the organizer
+        @type organizer: C{str}
+        @return: True if the autoAcceptGroup is assigned, and the organizer is a member
+            of that group.  False otherwise.
+        @rtype: C{bool}
+        """
+        if organizer is not None and self.record.autoAcceptGroup is not None:
+            organizerPrincipal = self.parent.principalForCalendarUserAddress(organizer)
+            if organizerPrincipal is not None:
+                if organizerPrincipal.record.guid in self.record.autoAcceptMembers():
+                    return True
+        return False
+
     def getCUType(self):
         return self.record.getCUType()
 

Modified: CalendarServer/trunk/twistedcaldav/directory/test/augments.xml
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/test/augments.xml	2012-11-08 23:22:14 UTC (rev 10008)
@@ -120,6 +120,7 @@
     <enable>true</enable>
     <enable-calendar>true</enable-calendar>
     <enable-addressbook>true</enable-addressbook>
+    <auto-accept-group>both_coasts</auto-accept-group>
   </record>
   <record>
     <uid>orion</uid>

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_directory.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -757,6 +757,30 @@
             }
         )
 
+    def test_autoAcceptMembers(self):
+        """
+        autoAcceptMembers( ) returns an empty list if no autoAcceptGroup is
+        assigned, or the expanded membership if assigned.
+        """
+
+        # No auto-accept-group for "orion" in augments.xml
+        orion = self.directoryService.recordWithGUID("orion")
+        self.assertEquals( orion.autoAcceptMembers(), [])
+
+        # "both_coasts" group assigned to "apollo" in augments.xml
+        apollo = self.directoryService.recordWithGUID("apollo")
+        self.assertEquals(
+            set(apollo.autoAcceptMembers()),
+            set([
+                "8B4288F6-CC82-491D-8EF9-642EF4F3E7D0",
+                 "5FF60DAD-0BDE-4508-8C77-15F0CA5C8DD1",
+                 "5A985493-EE2C-4665-94CF-4DFEA3A89500",
+                 "6423F94A-6B76-4A3A-815B-D52CFD77935D",
+                 "right_coast",
+                 "left_coast",
+            ])
+        )
+
 class RecordsMatchingTokensTests(TestCase):
 
     @inlineCallbacks

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_ldapdirectory.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -547,6 +547,7 @@
                     "autoScheduleAttr": None,
                     "proxyAttr": None,
                     "readOnlyProxyAttr": None,
+                    "autoAcceptGroupAttr": None,
                 },
                 "partitionSchema": {
                     "serverIdAttr": "server-id", # maps to augments server-id
@@ -762,6 +763,7 @@
                     "autoScheduleAttr": None,
                     "proxyAttr": None,
                     "readOnlyProxyAttr": None,
+                    "autoAcceptGroupAttr": None,
                 },
                 "partitionSchema": {
                     "serverIdAttr": "server-id", # maps to augments server-id
@@ -979,6 +981,7 @@
                     "autoScheduleAttr": None,
                     "proxyAttr": None,
                     "readOnlyProxyAttr": None,
+                    "autoAcceptGroupAttr": None,
                 },
                 "partitionSchema": {
                     "serverIdAttr": "server-id", # maps to augments server-id
@@ -1192,6 +1195,7 @@
                     "autoScheduleAttr": None,
                     "proxyAttr": None,
                     "readOnlyProxyAttr": None,
+                    "autoAcceptGroupAttr": None,
                 },
                 "partitionSchema": {
                     "serverIdAttr": "server-id", # maps to augments server-id
@@ -1363,7 +1367,7 @@
                      ])
             )
 
-            # Resource with delegates and autoSchedule = True
+            # Resource with delegates, autoSchedule = True, and autoAcceptGroup
 
             dn = "cn=odtestresource,cn=resources,dc=example,dc=com"
             guid = 'D3094652-344B-4633-8DB8-09639FA00FB6'
@@ -1382,6 +1386,8 @@
 <string>6C6CD280-E6E3-11DF-9492-0800200C9A66</string>
 <key>ReadOnlyCalendaringDelegate</key>
 <string>6AA1AE12-592F-4190-A069-547CD83C47C0</string>
+<key>AutoAcceptGroup</key>
+<string>77A8EB52-AA2A-42ED-8843-B2BEE863AC70</string>
 </dict>
 </dict>
 </plist>"""]
@@ -1394,6 +1400,8 @@
             self.assertEquals(record.externalReadOnlyProxies(),
                 set(['6AA1AE12-592F-4190-A069-547CD83C47C0']))
             self.assertTrue(record.autoSchedule)
+            self.assertEquals(record.autoAcceptGroup,
+                '77A8EB52-AA2A-42ED-8843-B2BEE863AC70')
 
             # Resource with no delegates and autoSchedule = False
 
@@ -1422,6 +1430,7 @@
             self.assertEquals(record.externalReadOnlyProxies(),
                 set())
             self.assertFalse(record.autoSchedule)
+            self.assertEquals(record.autoAcceptGroup, "")
 
 
             # Now switch off the resourceInfoAttr and switch to individual
@@ -1432,6 +1441,7 @@
                 "autoScheduleEnabledValue" : "yes",
                 "proxyAttr" : "proxy",
                 "readOnlyProxyAttr" : "read-only-proxy",
+                "autoAcceptGroupAttr" : "auto-accept-group",
             }
 
             # Resource with delegates and autoSchedule = True
@@ -1444,6 +1454,7 @@
                 'auto-schedule' : ['yes'],
                 'proxy' : ['6C6CD280-E6E3-11DF-9492-0800200C9A66'],
                 'read-only-proxy' : ['6AA1AE12-592F-4190-A069-547CD83C47C0'],
+                'auto-accept-group' : ['77A8EB52-AA2A-42ED-8843-B2BEE863AC70'],
             }
             record = self.service._ldapResultToRecord(dn, attrs,
                 self.service.recordType_resources)
@@ -1453,6 +1464,8 @@
             self.assertEquals(record.externalReadOnlyProxies(),
                 set(['6AA1AE12-592F-4190-A069-547CD83C47C0']))
             self.assertTrue(record.autoSchedule)
+            self.assertEquals(record.autoAcceptGroup,
+                '77A8EB52-AA2A-42ED-8843-B2BEE863AC70')
 
         def test_listRecords(self):
             """

Modified: CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/test/test_principal.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -560,7 +560,7 @@
         """
         DirectoryPrincipalResource.canAutoSchedule()
         """
-        
+
         # Set all resources and locations to auto-schedule, plus one user
         for provisioningResource, recordType, recordResource, record in self._allRecords():
             if record.enabledForCalendaring:
@@ -590,6 +590,27 @@
             if record.enabledForCalendaring:
                 self.assertFalse(recordResource.canAutoSchedule())
 
+
+    def test_canAutoScheduleAutoAcceptGroup(self):
+        """
+        DirectoryPrincipalResource.canAutoSchedule(organizer)
+        """
+
+        # Location "apollo" has an auto-accept group ("both_coasts") set in augments.xml,
+        # therefore any organizer in that group should be able to auto schedule
+
+        for provisioningResource, recordType, recordResource, record in self._allRecords():
+            if record.uid == "apollo":
+
+                # No organizer
+                self.assertFalse(recordResource.canAutoSchedule())
+
+                # Organizer in auto-accept group
+                self.assertTrue(recordResource.canAutoSchedule(organizer="mailto:wsanchez at example.com"))
+                # Organizer not in auto-accept group
+                self.assertFalse(recordResource.canAutoSchedule(organizer="mailto:a at example.com"))
+
+
     @inlineCallbacks
     def test_defaultAccessControlList_principals(self):
         """

Modified: CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/directory/xmlaugmentsparser.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -43,6 +43,7 @@
 ELEMENT_ENABLELOGIN       = "enable-login"
 ELEMENT_AUTOSCHEDULE      = "auto-schedule"
 ELEMENT_AUTOSCHEDULE_MODE = "auto-schedule-mode"
+ELEMENT_AUTOACCEPTGROUP   = "auto-accept-group"
 
 ATTRIBUTE_REPEAT          = "repeat"
 
@@ -60,6 +61,7 @@
     ELEMENT_ENABLELOGIN:       "enabledForLogin",
     ELEMENT_AUTOSCHEDULE:      "autoSchedule",
     ELEMENT_AUTOSCHEDULE_MODE: "autoScheduleMode",
+    ELEMENT_AUTOACCEPTGROUP:   "autoAcceptGroup",
 }
 
 class XMLAugmentsParser(object):
@@ -103,6 +105,7 @@
                     ELEMENT_PARTITIONID,
                     ELEMENT_HOSTEDAT,
                     ELEMENT_AUTOSCHEDULE_MODE,
+                    ELEMENT_AUTOACCEPTGROUP,
                 ):
                     fields[node.tag] = node.text if node.text else ""
                 elif node.tag in (

Modified: CalendarServer/trunk/twistedcaldav/scheduling/processing.py
===================================================================
--- CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2012-11-08 04:53:48 UTC (rev 10007)
+++ CalendarServer/trunk/twistedcaldav/scheduling/processing.py	2012-11-08 23:22:14 UTC (rev 10008)
@@ -500,8 +500,12 @@
             new_calendar = iTipProcessing.processNewRequest(self.message, self.recipient.cuaddr, creating=True)
 
             # Handle auto-reply behavior
-            if self.recipient.principal.canAutoSchedule():
-                send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, self.recipient.principal.getAutoScheduleMode()))
+            organizer = normalizeCUAddr(self.message.getOrganizer())
+            if self.recipient.principal.canAutoSchedule(organizer=organizer):
+                # auto schedule mode can depend on who the organizer is
+                mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
+                send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar,
+                    mode))
 
                 # Only store inbox item when reply is not sent or always for users
                 store_inbox = store_inbox or self.recipient.principal.getCUType() == "INDIVIDUAL"
@@ -533,8 +537,13 @@
             if new_calendar:
 
                 # Handle auto-reply behavior
-                if self.recipient.principal.canAutoSchedule():
-                    send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar, self.recipient.principal.getAutoScheduleMode()))
+                x = self.recipient.principal.canAutoSchedule()
+                organizer = normalizeCUAddr(self.message.getOrganizer())
+                if self.recipient.principal.canAutoSchedule(organizer=organizer):
+                    # auto schedule mode can depend on who the organizer is
+                    mode = self.recipient.principal.getAutoScheduleMode(organizer=organizer)
+                    send_reply, store_inbox, partstat = (yield self.checkAttendeeAutoReply(new_calendar,
+                        mode))
 
                     # Only store inbox item when reply is not sent or always for users
                     store_inbox = store_inbox or self.recipient.principal.getCUType() == "INDIVIDUAL"
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20121108/61c33b90/attachment-0001.html>


More information about the calendarserver-changes mailing list