Fix bug 586276 - Hooks / stubs mechanisms for Mozilla Lightning. r=philipp,a=philipp SEAMONKEY_2_7b2_BUILD1 SEAMONKEY_2_7b2_RELEASE
authorWolfgang Sourdeau <wsourdeau@inverse.ca>
Fri, 23 Dec 2011 14:46:55 +0100
changeset 9748 3b8a286fd1b0b852c54f108f6c7e809a156728a0
parent 9742 d51753596dcdba31c8dba3de3be07bf831b39931
child 9749 16370fb783c547a5c5e134a75c3a26d7daaa9b4b
child 9751 405c38f9cbece4f42c6959a24237e9ce349a128d
push idunknown
push userunknown
push dateunknown
reviewersphilipp, philipp
bugs586276
Fix bug 586276 - Hooks / stubs mechanisms for Mozilla Lightning. r=philipp,a=philipp
calendar/base/content/calendar-common-sets.js
calendar/base/content/calendar-item-editing.js
calendar/base/content/calendar-management.js
calendar/base/content/calendar-multiday-view.xml
calendar/base/content/calendar-ui-utils.js
calendar/base/content/calendar-unifinder.js
calendar/base/content/calendar-view-core.xml
calendar/base/content/dialogs/calendar-event-dialog.js
calendar/base/content/dialogs/calendar-summary-dialog.js
calendar/base/modules/calItipUtils.jsm
calendar/base/modules/calProviderUtils.jsm
calendar/base/public/Makefile.in
calendar/base/public/calICalendar.idl
calendar/base/public/calICalendarACLManager.idl
calendar/base/public/calIItemBase.idl
calendar/base/src/Makefile.in
calendar/base/src/calCachedCalendar.js
calendar/base/src/calDefaultACLManager.js
calendar/base/src/calDefaultACLManager.manifest
calendar/base/src/calItemBase.js
calendar/base/src/calTransactionManager.js
calendar/base/src/calUtils.js
calendar/lightning/content/lightning-utils.js
calendar/providers/caldav/calDavCalendar.js
calendar/providers/storage/calStorageCalendar.js
--- a/calendar/base/content/calendar-common-sets.js
+++ b/calendar/base/content/calendar-common-sets.js
@@ -30,16 +30,19 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+var CalendarDeleteCommandEnabled = false;
+var CalendarNewItemsCommandEnabled = false;
+
 /**
  * Command controller to execute calendar specific commands
  * @see nsICommandController
  */
 var calendarController = {
     defaultController: null,
 
     commands: {
@@ -126,40 +129,42 @@ var calendarController = {
         }
         return false;
     },
 
     isCommandEnabled: function cC_isCommandEnabled(aCommand) {
         switch (aCommand) {
             case "calendar_new_event_command":
             case "calendar_new_event_context_command":
-                return this.writable && this.calendars_support_events;
+                return CalendarNewItemsCommandEnabled && this.writable && this.calendars_support_events;
             case "calendar_modify_focused_item_command":
                 return this.item_selected;
             case "calendar_modify_event_command":
                 return this.item_selected;
             case "calendar_delete_focused_item_command":
-                return this.selected_items_writable;
+                return CalendarDeleteCommandEnabled && this.selected_items_writable;
             case "calendar_delete_event_command":
-                return this.selected_items_writable;
+                return CalendarDeleteCommandEnabled && this.selected_items_writable;
             case "calendar_new_todo_command":
             case "calendar_new_todo_context_command":
             case "calendar_new_todo_todaypane_command":
-                return this.writable && this.calendars_support_tasks;
+                return CalendarNewItemsCommandEnabled && this.writable && this.calendars_support_tasks;
             case "calendar_modify_todo_command":
             case "calendar_modify_todo_todaypane_command":
                  return this.todo_items_selected;
                  // This code is temporarily commented out due to
                  // bug 469684 Unifinder-todo: raising of the context menu fires blur-event
                  // this.todo_tasktree_focused;
             case "calendar_edit_calendar_command":
                 return this.isCalendarInForeground();
             case "calendar_task_filter_command":
                 return true;
             case "calendar_delete_todo_command":
+                if (!CalendarDeleteCommandEnabled)
+                    return false;
             case "calendar_toggle_completed_command":
             case "calendar_percentComplete-0_command":
             case "calendar_percentComplete-25_command":
             case "calendar_percentComplete-50_command":
             case "calendar_percentComplete-75_command":
             case "calendar_percentComplete-100_command":
             case "calendar_priority-0_command":
             case "calendar_priority-9_command":
@@ -456,21 +461,24 @@ var calendarController = {
     isInMode: function cC_isInMode(mode) {
         switch (mode) {
             case "mail":
                 return !isCalendarInForeground();
             case "calendar":
                 return isSunbird() || (gCurrentMode && gCurrentMode == "calendar");
             case "task":
                 return !isSunbird() && (gCurrentMode && gCurrentMode == "task");
-       }
+        }
+        return false;
     },
 
     onSelectionChanged: function cC_onSelectionChanged(aEvent) {
         var selectedItems = aEvent.detail;
+
+        calendarUpdateDeleteCommand(selectedItems);
         calendarController.item_selected = selectedItems && (selectedItems.length > 0);
 
         let selLength = (selectedItems === undefined ? 0 : selectedItems.length);
         let selected_events_readonly = 0;
         let selected_events_requires_network = 0;
         let selected_events_invitation = 0;
 
         if (selLength > 0) {
@@ -909,8 +917,52 @@ function minimonthPick(aNewDate) {
  */
 function selectAllItems() {
   if (calendarController.todo_tasktree_focused) {
     getTaskTree().selectAll();
   } else if (calendarController.isInMode("calendar")) {
     selectAllEvents();
   }
 }
+
+function calendarUpdateNewItemsCommand() {
+    let oldValue = CalendarNewItemsCommandEnabled;
+
+    let commands = ["calendar_new_event_command",
+                    "calendar_new_event_context_command",
+                    "calendar_new_todo_command",
+                    "calendar_new_todo_context_command",
+                    "calendar_new_todo_todaypane_command"];
+
+    CalendarNewItemsCommandEnabled = false;
+    let cal = getSelectedCalendar();
+    if (cal && isCalendarWritable(cal) && userCanAddItemsToCalendar(cal)) {
+        CalendarNewItemsCommandEnabled = true;
+    }
+
+    if (CalendarNewItemsCommandEnabled != oldValue) {
+        for (let i = 0; i < commands.length; i++) {
+            goUpdateCommand(commands[i]);
+        }
+    }
+}
+
+function calendarUpdateDeleteCommand(selectedItems) {
+    let oldValue = CalendarDeleteCommandEnabled;
+    CalendarDeleteCommandEnabled = (selectedItems.length > 0);
+
+    for each (let calendar in selectedItems) {
+        if (!userCanDeleteItemsFromCalendar(calendar)) {
+            CalendarDeleteCommandEnabled = false;
+        }
+    }
+
+    if (CalendarDeleteCommandEnabled != oldValue) {
+        let commands = ["calendar_delete_event_command",
+                        "calendar_delete_todo_command",
+                        "calendar_delete_focused_item_command",
+                        "button_delete",
+                        "cmd_delete"];
+        for each (let command in commands) {
+            goUpdateCommand(command);
+        }
+    }
+}
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -33,16 +33,17 @@
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 /**
  * Takes a job and makes sure the dispose function on it is called. If there is
  * no dispose function or the job is null, ignore it.
  *
  * @param job       The job to dispose.
  */
 function disposeJob(job) {
@@ -280,31 +281,53 @@ function openEventDialog(calendarItem, c
         isItemSupported = function isEventSupported(aCalendar) {
             return (aCalendar.getProperty("capabilities.events.supported") !== false);
         };
     }
 
     // Filter out calendars that don't support the given calendar item
     calendars = calendars.filter(isItemSupported);
 
-    if (mode == "new" && calendars.length < 1 &&
-        (!isCalendarWritable(calendar) || !isItemSupported(calendar))) {
-        // There are no writable calendars or no calendar supports the given
-        // item. Don't show the dialog.
-        disposeJob(job);
-        return;
-    } else if (mode == "new" &&
-               (!isCalendarWritable(calendar) || !isItemSupported(calendar))) {
-        // Pick the first calendar that supports the item and is writable
-        calendar = calendars[0];
-        if (calendarItem) {
-            // XXX The dialog currently uses the items calendar as a first
-            // choice. Since we are shortly before a release to keep regression
-            // risk low, explicitly set the item's calendar here.
-            calendarItem.calendar = calendars[0];
+    // Filter out calendar/items that we cannot write to/modify
+    if (mode == "new") {
+        calendars = calendars.filter(userCanAddItemsToCalendar);
+    } else { /* modify */
+        function calendarCanModifyItems(aCalendar) {
+            /* If the calendar is the item calendar, we check that the item
+             * can be modified. If the calendar is NOT the item calendar, we
+             * check that the user can remove items from that calendar and
+             * add items to the current one.
+             */
+            return (((calendarItem.calendar != aCalendar)
+                     && userCanDeleteItemsFromCalendar(calendarItem.calendar)
+                     && userCanAddItemsToCalendar(aCalendar))
+                    || ((calendarItem.calendar == aCalendar)
+                        && userCanModifyItem(calendarItem)));
+        }
+        calendars = calendars.filter(calendarCanModifyItems);
+    }
+
+    if (mode == "new"
+        && (!isCalendarWritable(calendar)
+            || !userCanAddItemsToCalendar(calendar)
+            || !isItemSupported(calendar))) {
+        if (calendars.length < 1) {
+            // There are no writable calendars or no calendar supports the given
+            // item. Don't show the dialog.
+            disposeJob(job);
+            return;
+        } else  {
+            // Pick the first calendar that supports the item and is writable
+            calendar = calendars[0];
+            if (calendarItem) {
+                // XXX The dialog currently uses the items calendar as a first
+                // choice. Since we are shortly before a release to keep
+                // regression risk low, explicitly set the item's calendar here.
+                calendarItem.calendar = calendars[0];
+            }
         }
     }
 
     // Setup the window arguments
     var args = new Object();
     args.calendarEvent = calendarItem;
     args.calendar = calendar;
     args.mode = mode;
@@ -325,22 +348,26 @@ function openEventDialog(calendarItem, c
 
     // ask the provide if this item is an invitation. if this is the case
     // we'll open the summary dialog since the user is not allowed to change
     // the details of the item.
     var isInvitation = false;
     if (calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         isInvitation = calendar.isInvitation(calendarItem);
     }
-
     // open the dialog modeless
-    var url = "chrome://calendar/content/calendar-event-dialog.xul";
-    if ((mode != "new" && isInvitation) || !isCalendarWritable(calendar)) {
+    let url;
+    if (isCalendarWritable(calendar)
+        && (mode == "new"
+            || (mode == "modify" && !isInvitation && userCanModifyItem((calendarItem))))) {
+        url = "chrome://calendar/content/calendar-event-dialog.xul";
+    } else {
         url = "chrome://calendar/content/calendar-summary-dialog.xul";
     }
+
     openDialog(url, "_blank", "chrome,titlebar,resizable", args);
 }
 
 /**
  * Prompts the user how the passed item should be modified. If the item is an
  * exception or already a parent item, the item is returned without prompting.
  * If "all occurrences" is specified, the parent item is returned. If "this
  * occurrence only" is specified, then aItem is returned. If "this and following
--- a/calendar/base/content/calendar-management.js
+++ b/calendar/base/content/calendar-management.js
@@ -236,16 +236,22 @@ var compositeObserver = {
         // more than one calendar
         document.commandDispatcher.updateCommands("calendar_commands");
     },
 
     onCalendarRemoved: function cO_onCalendarRemoved(aCalendar) {
         // Update commands to disallow deleting the last calendar and only
         // allowing reload remote calendars when there are remote calendars.
         document.commandDispatcher.updateCommands("calendar_commands");
+    },
+
+    onDefaultCalendarChanged: function cO_onDefaultCalendarChanged(aNewCalendar) {
+        // A new default calendar may mean that the new calendar has different
+        // ACLs. Make sure the commands are updated.
+        document.commandDispatcher.updateCommands("calendar_commands");
     }
 };
 
 /**
  * Opens the subscriptions dialog modally.
  */
 function openCalendarSubscriptionsDialog() {
     // the dialog will reset this to auto when it is done loading
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -1718,18 +1718,20 @@
         <parameter name="aOccurrence"/>
         <!-- "start", "end", "middle" -->
         <parameter name="aGrabbedElement"/>
         <!-- mouse screenX/screenY from the event -->
         <parameter name="aMouseX"/>
         <parameter name="aMouseY"/>
         <parameter name="aSnapInt"/>
         <body><![CDATA[
-          if (!isCalendarWritable(aOccurrence.calendar) ||
-              aOccurrence.calendar.getProperty("capabilities.events.supported") === false) {
+          if (!isCalendarWritable(aOccurrence.calendar)
+              || !userCanModifyItem(aOccurrence)
+              || (aOccurrence.calendar instanceof Components.interfaces.calISchedulingSupport && aOccurrence.calendar.isInvitation(aOccurrence))
+              || aOccurrence.calendar.getProperty("capabilities.events.supported") === false) {
               return;
           }
 
           //dump ("startSweepingToModify\n");
           this.mDragState = {
               origColumn: this,
               dragOccurrence: aOccurrence,
               mouseOffset: 0,
--- a/calendar/base/content/calendar-ui-utils.js
+++ b/calendar/base/content/calendar-ui-utils.js
@@ -310,16 +310,18 @@ function appendCalendarItems(aItem, aCal
     let calendars = sortCalendarArray(cal.getCalendarManager().getCalendars({}));
     let indexToSelect = 0;
     let index = -1;
     for (let i = 0; i < calendars.length; ++i) {
         let calendar = calendars[i];
         if (calendar.id == calendarToUse.id ||
             (calendar &&
              isCalendarWritable(calendar) &&
+             (userCanAddItemsToCalendar(calendar) ||
+              (calendar == aItem.calendar && userCanModifyItem(aItem))) &&
              isItemSupported(aItem, calendar))) {
             let menuitem = addMenuItem(aCalendarMenuParent, calendar.name, calendar.name);
             menuitem.calendar = calendar;
             index++;
             if (aOnCommand) {
                 menuitem.setAttribute("oncommand", aOnCommand);
             }
             if (aCalendarMenuParent.localName == "menupopup") {
--- a/calendar/base/content/calendar-unifinder.js
+++ b/calendar/base/content/calendar-unifinder.js
@@ -656,16 +656,18 @@ var unifinderTreeView = {
             return;
         }
 
         this.doingSelection = true;
 
         // If no items were passed, get the selected items from the view.
         aItemArray = aItemArray || currentView().getSelectedItems({});
 
+        calendarUpdateDeleteCommand(aItemArray);
+
         /**
          * The following is a brutal hack, caused by
          * http://lxr.mozilla.org/mozilla1.0/source/layout/xul/base/src/tree/src/nsTreeSelection.cpp#555
          * and described in bug 168211
          * http://bugzilla.mozilla.org/show_bug.cgi?id=168211
          * Do NOT remove anything in the next 3 lines, or the selection in the tree will not work.
          */
         this.treeElement.onselect = null;
--- a/calendar/base/content/calendar-view-core.xml
+++ b/calendar/base/content/calendar-view-core.xml
@@ -368,17 +368,19 @@
             onMouseOverItem(event);
         }
       ]]></handler>
       <handler event="draggesture"><![CDATA[
         if( event.target.localName == "calendar-event-box") {
             return;
         }
         let item = this.occurrence;
-        if (!isCalendarWritable(item.calendar)) {
+        if (!isCalendarWritable(item.calendar)
+            || !userCanModifyItem(item)
+            || (item.calendar instanceof Components.interfaces.calISchedulingSupport && item.calendar.isInvitation(item))) {
             return;
         }
         if (!this.selected)
             this.select(event);
         invokeEventDragSession(item, this);
       ]]></handler>
     </handlers>
   </binding>
--- a/calendar/base/content/dialogs/calendar-event-dialog.js
+++ b/calendar/base/content/dialogs/calendar-event-dialog.js
@@ -1921,16 +1921,22 @@ function attachmentLinkClicked(event) {
  * Update the dialog controls related related to the item's calendar.
  */
 function updateCalendar() {
     let item = window.calendarItem;
     let calendar = getCurrentCalendar();
 
     gIsReadOnly = calendar.readOnly;
 
+    // We might have to change the organizer, let's see
+    if (window.organizer) {
+      window.organizer.id = calendar.getProperty("organizerId");
+      window.organizer.commonName = calendar.getProperty("organizerCN");
+    }
+
     if (!canNotifyAttendees(calendar, item) && calendar.getProperty("imip.identity")) {
         enableElement("notify-attendees-checkbox");
     } else {
         disableElement("notify-attendees-checkbox");
     }
 
     // update the accept button
     updateAccept();
@@ -2223,17 +2229,17 @@ function updateToDoStatus(status, passed
           disableElement("percent-complete-textbox");
           disableElement("percent-complete-label");
           break;
       case "COMPLETED":
           document.getElementById("todo-status").selectedIndex = 3;
           enableElement("percent-complete-textbox");
           enableElement("percent-complete-label");
           // if there isn't a completedDate, set it to the previous value
-          if (!completedDate) { 
+          if (!completedDate) {
               completedDate = oldCompletedDate;
           }
           break;
       case "IN-PROCESS":
           document.getElementById("todo-status").selectedIndex = 2;
           disableElement("completed-date-picker");
           enableElement("percent-complete-textbox");
           enableElement("percent-complete-label");
@@ -2289,16 +2295,29 @@ function saveItem() {
         let notifyCheckbox = document.getElementById("notify-attendees-checkbox");
         if (notifyCheckbox.disabled || document.getElementById("event-grid-attendee-row-2").collapsed) {
             item.deleteProperty("X-MOZ-SEND-INVITATIONS");
         } else {
             item.setProperty("X-MOZ-SEND-INVITATIONS", notifyCheckbox.checked ? "TRUE" : "FALSE");
         }
     }
 
+    // We check if the organizerID is different from our
+    // calendar-user-address-set. The organzerID is the owner of the calendar.
+    // If it's different, that is because someone is acting on behalf of
+    // the organizer.
+    if (item.organizer && item.calendar.aclEntry) {
+        let userAddresses = item.calendar.aclEntry.getUserAddresses({});
+        if (userAddresses.length > 0
+            && !cal.attendeeMatchesAddresses(item.organizer, userAddresses)) {
+            let organizer = item.organizer.clone();
+            organizer.setProperty("SENT-BY", userAddresses[0]);
+            item.organizer = organizer;
+        }
+    }
     return item;
 }
 
 /**
  * Action to take when the user chooses to save. This can happen either by
  * saving directly or the user selecting to save after being prompted when
  * closing the dialog.
  *
@@ -2409,17 +2428,17 @@ function onCommandDeleteItem() {
             }
         };
 
         if (window.calendarItem.parentItem.recurrenceInfo && window.calendarItem.recurrenceId) {
             // if this is a single occurrence of a recurring item
             let newItem = window.calendarItem.parentItem.clone();
             newItem.recurrenceInfo.removeOccurrenceAt(window.calendarItem.recurrenceId);
 
-            window.opener.doTransaction("modify", newItem, newItem.calendar, 
+            window.opener.doTransaction("modify", newItem, newItem.calendar,
                                         window.calendarItem.parentItem, deleteListener);
         } else {
             window.opener.doTransaction("delete", window.calendarItem, window.calendarItem.calendar,
                                         null, deleteListener);
         }
     } else {
         gConfirmCancel = false;
         document.documentElement.cancelDialog();
--- a/calendar/base/content/dialogs/calendar-summary-dialog.js
+++ b/calendar/base/content/dialogs/calendar-summary-dialog.js
@@ -69,27 +69,31 @@ function onLoad() {
             self.onAccept();
 
             let item = window.calendarItem;
 
             // ...and close the window.
             window.close();
 
             return item;
-        }
+        };
     }
 
     // set the dialog-id to enable the right window-icon to be loaded.
     if (cal.isEvent(item)) {
         setDialogId(document.documentElement, "calendar-event-summary-dialog");
     } else if (cal.isToDo(item)) {
         setDialogId(document.documentElement, "calendar-task-summary-dialog");
     }
 
-    window.readOnly = calendar.readOnly;
+    window.readOnly = !(isCalendarWritable(calendar)
+                        && (userCanModifyItem(item)
+                            || (calInstanceOf(item.calendar, Components.interfaces.calISchedulingSupport)
+                                && item.calendar.isInvitation(item)
+                                && userCanRespondToInvitation(item))));
     if (!window.readOnly && calInstanceOf(calendar, Components.interfaces.calISchedulingSupport)) {
         var attendee = calendar.getInvitedAttendee(item);
         if (attendee) {
             // if this is an unresponded invitation, preset our default alarm values:
             if (!item.getAlarms({}).length &&
                 (attendee.participationStatus == "NEEDS-ACTION")) {
                 cal.alarms.setDefaultValues(item);
             }
@@ -186,18 +190,18 @@ function onLoad() {
     }
 
     window.focus();
     opener.setCursor("auto");
 }
 
 /**
  * Saves any changed information to the item.
- * 
- * @return      Returns true if the dialog 
+ *
+ * @return      Returns true if the dialog
  */
 function onAccept() {
     dispose();
     if (window.readOnly) {
         return true;
     }
     var args = window.arguments[0];
     var oldItem = args.calendarEvent;
@@ -263,33 +267,33 @@ function updateRepeatDetails() {
     // set recurrence info. bail out if there's more
     // than a single rule or something other than a rule.
     var recurrenceInfo = item.recurrenceInfo;
     if (!recurrenceInfo) {
         return;
     }
 
     document.getElementById("repeat-row").removeAttribute("hidden");
-    
+
     // First of all collapse the details text. If we fail to
     // create a details string, we simply don't show anything.
     // this could happen if the repeat rule is something exotic
     // we don't have any strings prepared for.
     var repeatDetails = document.getElementById("repeat-details");
     repeatDetails.setAttribute("collapsed", "true");
-    
+
     // Try to create a descriptive string from the rule(s).
     var kDefaultTimezone = calendarDefaultTimezone();
     var startDate =  item.startDate || item.entryDate;
     var endDate = item.endDate || item.dueDate;
     startDate = startDate ? startDate.getInTimezone(kDefaultTimezone) : null;
     endDate = endDate ? endDate.getInTimezone(kDefaultTimezone) : null;
     var detailsString = recurrenceRule2String(
         recurrenceInfo, startDate, endDate, startDate.isDate);
-        
+
     // Now display the string...
     if (detailsString) {
         var lines = detailsString.split("\n");
         repeatDetails.removeAttribute("collapsed");
         while (repeatDetails.childNodes.length > lines.length) {
             repeatDetails.removeChild(repeatDetails.lastChild);
         }
         var numChilds = repeatDetails.childNodes.length;
--- a/calendar/base/modules/calItipUtils.jsm
+++ b/calendar/base/modules/calItipUtils.jsm
@@ -176,24 +176,28 @@ cal.itip = {
         if (imipMethod && imipMethod.length != 0 && imipMethod.toLowerCase() != "nomethod") {
             itipItem.receivedMethod = imipMethod.toUpperCase();
         } else { // There is no METHOD in the content-type header (spec violation).
                  // Fall back to using the one from the itipItem's ICS.
             imipMethod = itipItem.receivedMethod;
         }
         cal.LOG("iTIP method: " + imipMethod);
 
-        let writableCalendars = cal.getCalendarManager().getCalendars({}).filter(cal.itip.isSchedulingCalendar);
+        function isWritableCalendar(aCalendar) {
+            /* TODO: missing ACL check for existing items (require callback API) */
+            return (cal.itip.isSchedulingCalendar(aCalendar)
+                    && cal.userCanAddItemsToCalendar(aCalendar));
+        }
+        let writableCalendars = cal.getCalendarManager().getCalendars({}).filter(isWritableCalendar);
         if (writableCalendars.length > 0) {
             let compCal = Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
                                     .createInstance(Components.interfaces.calICompositeCalendar);
             writableCalendars.forEach(compCal.addCalendar, compCal);
             itipItem.targetCalendar = compCal;
         }
-
     },
 
     /**
      * Scope: iTIP message receiver
      *
      * Gets the suggested text to be shown when an imip item has been processed.
      * This text is ready localized and can be displayed to the user.
      *
@@ -434,17 +438,17 @@ cal.itip = {
             case "REFRESH":
             case "REQUEST:UPDATE":
             case "REQUEST:UPDATE-MINOR":
             case "PUBLISH:UPDATE":
             case "REPLY":
             case "CANCEL":
                 needsCalendar = false;
                 break;
-            default: 
+            default:
                 needsCalendar = true;
                 break;
         }
 
         if (needsCalendar) {
             let calendars = cal.getCalendarManager().getCalendars({}).filter(cal.itip.isSchedulingCalendar);
 
             if (aItipItem.receivedMethod == "REQUEST") {
@@ -475,17 +479,17 @@ cal.itip = {
                 args.calendars = calendars;
                 args.onOk = function selectCalendar(aCal) { targetCalendar = aCal; };
                 args.promptText = cal.calGetString("calendar", "importPrompt");
                 aWindow.openDialog("chrome://calendar/content/chooseCalendarDialog.xul",
                                    "_blank", "chrome,titlebar,modal,resizable", args);
             }
 
             if (targetCalendar) {
-              aItipItem.targetCalendar = targetCalendar;
+                aItipItem.targetCalendar = targetCalendar;
             }
         }
 
         return (!needsCalendar || targetCalendar != null);
     },
 
     /**
      * Scope: iTIP message receiver
@@ -583,16 +587,27 @@ cal.itip = {
         }
 
         let autoResponse = { value: false }; // controls confirm to send email only once
 
         let invitedAttendee = ((cal.calInstanceOf(aItem.calendar, Components.interfaces.calISchedulingSupport) &&
                                 aItem.calendar.isInvitation(aItem))
                                ? aItem.calendar.getInvitedAttendee(aItem) : null);
         if (invitedAttendee) { // actually is an invitation copy, fix attendee list to send REPLY
+            /* We check if the attendee id matches one of of the
+             * userAddresses. If they aren't equal, it means that
+             * someone is accepting invitations on behalf of an other user. */
+            if (aItem.calendar.aclEntry) {
+                let userAddresses = aItem.calendar.aclEntry.getUserAddresses({});
+                if (userAddresses.length > 0
+                    && !cal.attendeeMatchesAddresses(invitedAttendee, userAddresses)) {
+                    invitedAttendee.setProperty("SENT-BY", userAddresses[0]);
+                }
+            }
+
             if (aItem.organizer) {
                 let origInvitedAttendee = (aOriginalItem && aOriginalItem.getAttendeeById(invitedAttendee.id));
 
                 if (aOpType == Components.interfaces.calIOperationListener.DELETE) {
                     // in case the attendee has just deleted the item, we want to send out a DECLINED REPLY:
                     origInvitedAttendee = invitedAttendee;
                     invitedAttendee = invitedAttendee.clone();
                     invitedAttendee.participationStatus = "DECLINED";
--- a/calendar/base/modules/calProviderUtils.jsm
+++ b/calendar/base/modules/calProviderUtils.jsm
@@ -551,16 +551,17 @@ cal.ProviderBase.prototype = {
         return cal.doQueryInterface(this, cal.ProviderBase.prototype, aIID,
                                     [Components.interfaces.nsISupports,
                                      Components.interfaces.calICalendar,
                                      Components.interfaces.calISchedulingSupport]);
     },
 
     mID: null,
     mUri: null,
+    mACLEntry: null,
     mObservers: null,
     mProperties: null,
 
     initProviderBase: function cPB_initProviderBase() {
         this.wrappedJSObject = this;
         this.mObservers = new cal.ObserverBag(Components.interfaces.calIObserver);
         this.mProperties = {};
         this.mProperties.currentStatus = Components.results.NS_OK;
@@ -614,16 +615,31 @@ cal.ProviderBase.prototype = {
     // attribute AUTF8String name;
     get name() {
         return this.getProperty("name");
     },
     set name(aValue) {
         return this.setProperty("name", aValue);
     },
 
+    // readonly attribute calICalendarACLManager aclManager;
+    get aclManager() {
+        const defaultACLProviderClass = "@mozilla.org/calendar/acl-manager;1?type=default";
+        let providerClass = this.getProperty("aclManagerClass");
+        if (!providerClass || !Components.classes[providerClass]) {
+            providerClass = defaultACLProviderClass;
+        }
+        return Components.classes[providerClass].getService(Components.interfaces.calICalendarACLManager);
+    },
+
+    // readonly attribute calICalendarACLEntry aclEntry;
+    get aclEntry() {
+        return this.mACLEntry;
+    },
+
     // attribute calICalendar superCalendar;
     get superCalendar() {
         // If we have a superCalendar, check this calendar for a superCalendar.
         // This will make sure the topmost calendar is returned
         return (this.mSuperCalendar ? this.mSuperCalendar.superCalendar : this);
     },
     set superCalendar(val) {
         return (this.mSuperCalendar = val);
@@ -828,28 +844,72 @@ cal.ProviderBase.prototype = {
 
     // void removeObserver( in calIObserver observer );
     removeObserver: function cPB_removeObserver(aObserver) {
         this.mObservers.remove(aObserver);
     },
 
     // calISchedulingSupport: Implementation corresponding to our iTIP/iMIP support
     isInvitation: function cPB_isInvitation(aItem) {
-        let id = this.getProperty("organizerId");
-        if (id) {
-            let org = aItem.organizer;
-            if (!org || (org.id.toLowerCase() == id.toLowerCase())) {
+        if (!this.mACLEntry || !this.mACLEntry.hasAccessControl) {
+            // No ACL support - fallback to the old method
+            let id = this.getProperty("organizerId");
+            if (id) {
+                let org = aItem.organizer;
+                if (!org || (org.id.toLowerCase() == id.toLowerCase())) {
+                    return false;
+                }
+                return (aItem.getAttendeeById(id) != null);
+            }
+            return false;
+        }
+
+        let org = aItem.organizer;
+        if (!org) {
+            // HACK
+            // if we don't have an organizer, this is perhaps because it's an exception
+            // to a recurring event. We check the parent item.
+            if (aItem.parentItem) {
+                org = aItem.parentItem.organizer;
+                if (!org) return false;
+            } else {
                 return false;
             }
-            return (aItem.getAttendeeById(id) != null);
         }
+
+        // We check if :
+        // - the organizer of the event is NOT within the owner's identities of this calendar
+        // - if the one of the owner's identities of this calendar is in the attendees
+        let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
+        for (let i = 0; i < ownerIdentities.length; i++) {
+            let identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
+            if (org.id.toLowerCase() == identity)
+                return false;
+
+            if (aItem.getAttendeeById(identity) != null)
+                return true;
+        }
+
         return false;
     },
 
     getInvitedAttendee: function cPB_getInvitedAttendee(aItem) {
         let id = this.getProperty("organizerId");
-        return (id ? aItem.getAttendeeById(id) : null);
+        let attendee = (id ? aItem.getAttendeeById(id) : null);
+
+        if (!attendee && this.mACLEntry && this.mACLEntry.hasAccessControl) {
+            let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
+            if (ownerIdentities.length > 0) {
+                let identity;
+                for (let i = 0; !attendee && i < ownerIdentities.length; i++) {
+                    identity = "mailto:" + ownerIdentities[i].email.toLowerCase();
+                    attendee = aItem.getAttendeeById(identity);
+                }
+            }
+        }
+
+        return attendee;
     },
 
     canNotify: function cPB_canNotify(aMethod, aItem) {
         return false; // use outbound iTIP for all
     }
 };
--- a/calendar/base/public/Makefile.in
+++ b/calendar/base/public/Makefile.in
@@ -42,17 +42,19 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= calbase
 XPIDL_MODULE	= calbase
 
-XPIDLSRCS = calIAlarm.idl \
+
+XPIDLSRCS = calICalendarACLManager.idl \
+            calIAlarm.idl \
             calIAlarmService.idl \
             calIAttachment.idl \
             calIAttendee.idl \
             calICalendar.idl \
             calICalendarManager.idl \
             calICalendarProvider.idl \
             calICalendarSearchProvider.idl \
             calICalendarView.idl \
--- a/calendar/base/public/calICalendar.idl
+++ b/calendar/base/public/calICalendar.idl
@@ -46,16 +46,18 @@
 
 // decls for stuff from other files
 interface nsIURI;
 interface calIItemBase;
 interface nsIVariant;
 interface nsISimpleEnumerator;
 
 // forward decls for this file
+interface calICalendarACLManager;
+interface calICalendarACLEntry;
 interface calIObserver;
 interface calIOperationListener;
 interface calIRange;
 interface calIDateTime;
 interface calIOperation;
 interface calIStatusObserver;
 interface nsIDOMChromeWindow;
 
@@ -83,17 +85,28 @@ interface calICalendar : nsISupports
    */
   readonly attribute AUTF8String type;
 
   /**
    * If this calendar is provided by an extension, this attribute should return
    * the extension's id, otherwise null.
    */
   readonly attribute AString providerID;
-  
+
+  /**
+   * Returns the acl manager for the calendar, based on the "aclManagerClass"
+   * property. If this property is not defined, the default manager is used
+   */
+  readonly attribute calICalendarACLManager aclManager;
+
+  /**
+   * Returns the acl entry associated to the calendar.
+   */
+  readonly attribute calICalendarACLEntry aclEntry;
+
   /**
    * Multiple calendar instances may be composited, logically acting as a
    * single calendar, e.g. for caching puorposing.
    * This attribute determines the topmost calendar that returned items should
    * belong to. If the current instance is the topmost calendar, then it should
    * be returned directly.
    *
    * @see calIItemBase::calendar
new file mode 100644
--- /dev/null
+++ b/calendar/base/public/calICalendarACLManager.idl
@@ -0,0 +1,121 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Inverse Inc. code.
+ *
+ * The Initial Developers of the Original Code are
+ *   Wolfgang Sourdeau <wsourdeau@inverse.ca>
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsISupports.idl"
+
+interface nsIMsgIdentity;
+interface nsIURI;
+
+interface calICalendar;
+interface calIItemBase;
+interface calIOperationListener;
+
+interface calIItemACLEntry;
+
+/**
+ */
+[scriptable, uuid(a64bd8a0-e9f0-4f64-928a-1c98861e4703)]
+interface calICalendarACLManager : nsISupports
+{
+    /* Gets the calICalendarACLEntry of the current user for the specified
+       calendar. */
+    void getCalendarEntry(in calICalendar aCalendar,
+                          in calIOperationListener aListener);
+
+    /* Gets the calIItemACLEntry of the current user for the specified
+       calendar item. Depending on the implementation, each item can have
+       different permissions based on specific attributes.
+       (TODO: should be made asynchronous one day) */
+    calIItemACLEntry getItemEntry(in calIItemBase aItem);
+};
+
+[scriptable, uuid(f3da7954-52a4-45a9-bd7d-96c518133d0c)]
+interface calICalendarACLEntry : nsISupports
+{
+    /* The calICalendarACLManager instance that generated this entry. */
+    readonly attribute calICalendarACLManager aclManager;
+
+    /* Whether the underlying calendar does have access control. */
+    readonly attribute boolean hasAccessControl;
+
+    /* Whether the user accessing the calendar is its owner. */
+    readonly attribute boolean userIsOwner;
+
+    /* Whether the user accessing the calendar can add items to it. */
+    readonly attribute boolean userCanAddItems;
+
+    /* Whether the user accessing the calendar can remove items from it. */
+    readonly attribute boolean userCanDeleteItems;
+
+    /* Returns the list of user ids matching the user accessing the
+       calendar. */
+    void getUserAddresses(out PRUint32 aCount,
+                          [array, size_is(aCount), retval] out AUTF8String aAddresses);
+
+    /* Returns the list of instantiated identities for the user accessing the
+       calendar. */
+    void getUserIdentities(out PRUint32 aCount,
+                           [array, size_is(aCount), retval] out nsIMsgIdentity aIdentities);
+    /* Returns the list of instantiated identities for the user representing
+       the calendar owner. */
+    void getOwnerIdentities(out PRUint32 aCount,
+                            [array, size_is(aCount), retval] out nsIMsgIdentity aIdentities);
+
+    /* Helper method that forces a cleanup of any cache and a reload of the
+       current entry.
+       (TODO: should be made asynchronous one day) */
+    void refresh();
+};
+
+[scriptable, uuid(4d0b7ced-8c57-4efa-87e7-8dd5b7481312)]
+interface calIItemACLEntry : nsISupports
+{
+    /* The parent calICalendarACLEntry instance. */
+    readonly attribute calICalendarACLEntry calendarEntry;
+
+    /* Whether the active user can fully modify the item. */
+    readonly attribute boolean userCanModify;
+
+    /* Whether the active user can respond to this item, if it is an invitation. */
+    readonly attribute boolean userCanRespond;
+
+    /* Whether the active user can view all the item properties. */
+    readonly attribute boolean userCanViewAll;
+
+    /* Whether the active user can only see when this item occurs without
+       knowing any details. */
+    readonly attribute boolean userCanViewDateAndTime;
+};
--- a/calendar/base/public/calIItemBase.idl
+++ b/calendar/base/public/calIItemBase.idl
@@ -39,16 +39,17 @@
 
 #include "nsISupports.idl"
 
 interface nsISimpleEnumerator;
 interface nsIVariant;
 
 interface nsIPropertyBag;
 
+interface calIItemACLEntry;
 interface calIAlarm;
 interface calIAttachment;
 interface calIAttendee;
 interface calICalendar;
 interface calIDateTime;
 interface calIDuration;
 interface calIIcalComponent;
 interface calIRecurrenceInfo;
@@ -90,16 +91,21 @@ interface calIItemBase : nsISupports
    * this one, by testing both the id and recurrenceId property.  This
    *
    * @arg aItem     the item to compare against this one
    *
    * @return        true if both ids match, false otherwise
    */
   boolean hasSameIds(in calIItemBase aItem);
 
+  /**
+   * Returns the acl entry associated to the item.
+   */
+  readonly attribute calIItemACLEntry aclEntry;
+
   //
   // the generation number of this item
   //
   attribute PRUint32 generation;
 
   // the time when this item was created
   readonly attribute calIDateTime creationDate;
 
--- a/calendar/base/src/Makefile.in
+++ b/calendar/base/src/Makefile.in
@@ -69,16 +69,18 @@ CPPSRCS = calDateTime.cpp \
 	  calUtils.cpp \
 	  $(NULL)
 
 EXTRA_COMPONENTS = \
     calItemModule.manifest \
     calItemModule.js \
     calTimezoneService.manifest \
     calTimezoneService.js \
+    calDefaultACLManager.js \
+    calDefaultACLManager.manifest \
     $(NULL)
 
 EXTRA_SCRIPTS = \
     calAlarm.js \
     calAlarmService.js \
     calAlarmMonitor.js \
     calAttachment.js \
     calAttendee.js \
--- a/calendar/base/src/calCachedCalendar.js
+++ b/calendar/base/src/calCachedCalendar.js
@@ -857,13 +857,13 @@ calCachedCalendar.prototype = {
         functions.forEach(defineForwardFunction);
         getters.forEach(defineForwardGetter);
         gettersAndSetters.forEach(defineForwardGetterAndSetter);
     }
 
     defineForwards(calCachedCalendar.prototype, "mUncachedCalendar",
                    ["getProperty", "setProperty", "deleteProperty",
                     "isInvitation", "getInvitedAttendee", "canNotify"],
-                   ["type"],
+                   ["type", "aclManager", "aclEntry"],
                    ["id", "name", "uri", "readOnly"]);
     defineForwards(calCachedCalendar.prototype, "mCachedCalendar",
                    ["getItem", "getItems", "startBatch", "endBatch"], [], []);
 })();
new file mode 100644
--- /dev/null
+++ b/calendar/base/src/calDefaultACLManager.js
@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (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.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Inverse inc. code.
+ *
+ * The Initial Developer of the Original Code is
+ *  Wolfgang Sourdeau  <wsourdeau@inverse.ca>
+ * Portions created by the Initial Developer are
+ *  Copyright (C) 2008-2011 Inverse inc. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+Components.utils.import("resource://calendar/modules/calUtils.jsm");
+
+/* calDefaultACLManager */
+function calDefaultACLManager() {
+    this.mCalendarEntries = {};
+}
+
+calDefaultACLManager.prototype = {
+    mCalendarEntries: null,
+
+    /* nsISupports */
+    classID: Components.ID("{7463258c-6ef3-40a2-89a9-bb349596e927}"),
+    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calICalendarACLManager]),
+
+    /* nsIClassInfo */
+    classInfo: XPCOMUtils.generateCI({
+        classID: Components.ID("{7463258c-6ef3-40a2-89a9-bb349596e927}"),
+        contractID: "@mozilla.org/calendar/acl-manager;1?type=default",
+        classDescription: "Default Calendar ACL Provider",
+        interfaces: [Components.interfaces.calICalendarACLManager],
+        flags: Components.interfaces.nsIClassInfo.SINGLETON,
+    }),
+
+    /* calICalendarACLManager */
+    _getCalendarEntryCached: function cDACLM__getCalendarEntryCached(aCalendar) {
+        let calUri = aCalendar.uri.spec;
+        if (!(calUri in this.mCalendarEntries)) {
+            this.mCalendarEntries[calUri] = new calDefaultCalendarACLEntry(this);
+        }
+
+        return this.mCalendarEntries[calUri];
+    },
+    getCalendarEntry: function cDACLM_getCalendarEntry(aCalendar, aListener) {
+        let entry = this._getCalendarEntryCached(aCalendar);
+        aListener.onOperationComplete(aCalendar, Components.results.NS_OK,
+                                      Components.interfaces.calIOperationListener.GET,
+                                      null,
+                                      entry);
+    },
+    getItemEntry: function cDACLM_getItemEntry(aItem) {
+        let calEntry = this._getCalendarEntryCached(aItem.calendar);
+        return new calDefaultItemACLEntry(calEntry);
+    },
+
+};
+
+function calDefaultCalendarACLEntry(aMgr) {
+    this.mACLManager = aMgr;
+}
+
+calDefaultCalendarACLEntry.prototype = {
+    mACLManager: null,
+
+    /* nsISupports */
+    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calICalendarACLEntry]),
+
+    /* calICalendarACLCalendarEntry */
+    get aclManager() {
+        return this.mACLManager;
+    },
+
+    hasAccessControl: false,
+    userIsOwner: true,
+    userCanAddItems: true,
+    userCanDeleteItems: true,
+
+    _getIdentities: function calDefaultCalendarACLEntry_getUserAddresses(aCount) {
+        let identities = [];
+        cal.calIterateEmailIdentities(function (id, ac) { identities.push(id); });
+        aCount.value = identities.length;
+        return identities;
+    },
+
+    getUserAddresses: function calDefaultCalendarACLEntry_getUserAddresses(aCount) {
+        let identities = this._getIdentities(aCount);
+        let addresses = [ id.email for each (id in identities) ];
+        return addresses;
+    },
+
+    getUserIdentities: function calDefaultCalendarACLEntry_getUserIdentities(aCount) {
+        return this._getIdentities(aCount)
+    },
+    getOwnerIdentities: function calDefaultCalendarACLEntry_getOwnerIdentities(aCount) {
+        return this._getIdentities(aCount)
+    },
+
+    refresh: function calDefaultCalendarACLEntry_refresh() {
+    }
+};
+
+function calDefaultItemACLEntry(aCalendarEntry) {
+    this.calendarEntry = aCalendarEntry;
+};
+
+calDefaultItemACLEntry.prototype = {
+    /* nsISupports */
+    QueryInterface: XPCOMUtils.generateQI([Components.interfaces.calIItemACLEntry]),
+
+    /* calIItemACLEntry */
+    calendarEntry: null,
+    userCanModify: true,
+    userCanRespond: true,
+    userCanViewAll: true,
+    userCanViewDateAndTime: true,
+};
+
+/** Module Registration */
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([calDefaultACLManager]);
new file mode 100644
--- /dev/null
+++ b/calendar/base/src/calDefaultACLManager.manifest
@@ -0,0 +1,2 @@
+component {7463258c-6ef3-40a2-89a9-bb349596e927} calDefaultACLManager.js
+contract @mozilla.org/calendar/acl-manager;1?type=default {7463258c-6ef3-40a2-89a9-bb349596e927}
--- a/calendar/base/src/calItemBase.js
+++ b/calendar/base/src/calItemBase.js
@@ -70,16 +70,18 @@ calItemBase.prototype = {
     mAlarms: null,
     mAlarmLastAck: null,
 
     mAttendees: null,
     mAttachments: null,
     mRelations: null,
     mCategories: null,
 
+    mACLEntry: null,
+
     /**
      * Initialize the base item's attributes. Can be called from inheriting
      * objects in their constructor.
      */
     initItemBase: function cIB_initItemBase() {
         this.wrappedJSObject = this;
         this.mProperties = new calPropertyBag();
         this.mPropertyParams = {};
@@ -92,16 +94,32 @@ calItemBase.prototype = {
     QueryInterface: function cIB_QueryInterface(aIID) {
         return doQueryInterface(this, calItemBase.prototype, aIID,
                                 [Components.interfaces.calIItemBase]);
     },
 
     /**
      * @see calIItemBase
      */
+    get aclEntry() {
+        let aclEntry = this.mACLEntry;
+        let aclManager = (this.calendar && this.calendar.superCalendar.aclManager);
+
+        if (!aclEntry && aclManager) {
+            this.mACLEntry = aclManager.getItemEntry(this);
+            aclEntry = this.mACLEntry;
+        }
+
+        if (!aclEntry && this.parentItem != this) {
+            // No ACL entry on this item, check the parent
+            aclEntry = this.parentItem.aclEntry;
+        }
+
+        return aclEntry;
+    },
 
     // readonly attribute AUTF8String hashId;
     get hashId() {
         if (this.mHashId === null) {
             var rid = this.recurrenceId;
             var calendar = this.calendar;
             // some unused delim character:
             this.mHashId = [encodeURIComponent(this.id),
@@ -260,16 +278,17 @@ calItemBase.prototype = {
      * Clones the base item's properties into the passed object, potentially
      * setting a new parent item.
      *
      * @param m     The item to clone this item into
      * @param aNewParent    (optional) The new parent item to set on m.
      */
     cloneItemBaseInto: function cIB_cloneItemBaseInto(m, aNewParent) {
         m.mImmutable = false;
+        m.mACLEntry = this.mACLEntry;
         m.mIsProxy = this.mIsProxy;
         m.mParentItem = (calTryWrappedJSObject(aNewParent) || this.mParentItem);
         m.mHashId = this.mHashId;
         m.mCalendar = this.mCalendar;
         if (this.mRecurrenceInfo) {
             m.mRecurrenceInfo = calTryWrappedJSObject(this.mRecurrenceInfo.clone());
             m.mRecurrenceInfo.item = m;
         }
@@ -521,18 +540,17 @@ calItemBase.prototype = {
     //                   [array,size_is(count),retval] out calIAttendee attendees);
     getAttendees: function cIB_getAttendees(countObj) {
         if (!this.mAttendees && this.mIsProxy) {
             this.mAttendees = this.mParentItem.getAttendees(countObj);
         }
         if (this.mAttendees) {
             countObj.value = this.mAttendees.length;
             return this.mAttendees.concat([]); // clone
-        }
-        else {
+        } else {
             countObj.value = 0;
             return [];
         }
     },
 
     // calIAttendee getAttendeeById(in AUTF8String id);
     getAttendeeById: function cIB_getAttendeeById(id) {
         var attendees = this.getAttendees({});
@@ -1088,23 +1106,25 @@ makeMemberAttr(calItemBase, "mProperties
  * @param dflt          The default value in case none is set
  * @param attr          The attribute name to be used
  * @param asProperty    If true, getProperty will be used to get/set the
  *                        member.
  */
 function makeMemberAttr(ctor, varname, dflt, attr, asProperty) {
     // XXX handle defaults!
     var getter = function () {
-        if (asProperty)
+        if (asProperty) {
             return this.getProperty(varname);
-        else
+        } else {
             return (varname in this ? this[varname] : undefined);
+        }
     };
     var setter = function (v) {
         this.modify();
-        if (asProperty)
+        if (asProperty) {
             return this.setProperty(varname, v);
-        else
+        } else {
             return (this[varname] = v);
+        }
     };
     ctor.prototype.__defineGetter__(attr, getter);
     ctor.prototype.__defineSetter__(attr, setter);
 }
--- a/calendar/base/src/calTransactionManager.js
+++ b/calendar/base/src/calTransactionManager.js
@@ -93,17 +93,17 @@ calTransactionManager.prototype = {
 
     checkWritable: function cTM_checkWritable(transaction) {
         if (transaction) {
             transaction = transaction.wrappedJSObject;
             if (transaction) {
                 function checkItem(item) {
                     if (item) {
                         var calendar = item.calendar;
-                        if (calendar && !isCalendarWritable(calendar)) {
+                        if (calendar && (!isCalendarWritable(calendar) || !userCanAddItemsToCalendar(calendar))) {
                             return false;
                         }
                     }
                     return true;
                 }
 
                 if (!checkItem(transaction.mItem) ||
                     !checkItem(transaction.mOldItem)) {
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -295,16 +295,83 @@ function isCalendarWritable(aCalendar) {
     return (!aCalendar.getProperty("disabled") &&
             !aCalendar.readOnly &&
             (!getIOService().offline ||
              aCalendar.getProperty("cache.enabled") ||
              aCalendar.getProperty("requiresNetwork") === false));
 }
 
 /**
+ * Check if the specified calendar is writable from an ACL point of view.
+ *
+ * @param aCalendar     The calendar to check
+ * @return              True if the calendar is writable
+ */
+function userCanAddItemsToCalendar(aCalendar) {
+    let aclEntry = aCalendar.aclEntry;
+    return (!aclEntry || !aclEntry.hasAccessControl || aclEntry.userIsOwner || aclEntry.userCanAddItems);
+}
+
+/**
+ * Check if the user can delete items from the specified calendar, from an ACL point of view.
+ *
+ * @param aCalendar     The calendar to check
+ * @return              True if the calendar is writable
+ */
+function userCanDeleteItemsFromCalendar(aCalendar) {
+    let aclEntry = aCalendar.aclEntry;
+    return (!aclEntry || !aclEntry.hasAccessControl || aclEntry.userIsOwner || aclEntry.userCanDeleteItems);
+}
+
+/**
+ * Check if the user can fully modify the specified item, from an ACL point of view.
+ * Note to be confused with the right to respond to an invitation, which is
+ * handled instead by userCanRespondToInvitation.
+ *
+ * @param aItem         The calendar item to check
+ * @return              True if the item is modifiable
+ */
+function userCanModifyItem(aItem) {
+    let aclEntry = aItem.aclEntry;
+    return (!aclEntry || !aclEntry.calendarEntry.hasAccessControl || aclEntry.calendarEntry.userIsOwner || aclEntry.userCanModify);
+}
+
+/**
+ * Check if the attendee object matches one of the addresses in the list. This
+ * is useful to determine whether the current user acts as a delegate.
+ *
+ * @param aAttendee     The reference attendee object
+ * @param addresses     The list of addresses
+ * @return              True if there is a match
+ */
+function attendeeMatchesAddresses(anAttendee, addresses) {
+    let attId = anAttendee.id.toLowerCase();
+    for each (let address in addresses) {
+        if (attId == address.toLowerCase()) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+/**
+ * Check if the user can fully modify the specified item, from an ACL point of view.
+ * Note to be confused with the right to respond to an invitation, which is
+ * handled instead by userCanRespondToInvitation.
+ *
+ * @param aItem         The calendar item to check
+ * @return              True if the item is modifiable
+ */
+function userCanRespondToInvitation(aItem) {
+    let aclEntry = aItem.aclEntry;
+    return userCanModifyItem(aItem) || aclEntry.userCanRespond;
+}
+
+/**
  * Opens the Create Calendar wizard
  *
  * @param aCallback  a function to be performed after calendar creation
  */
 function openCalendarWizard(aCallback) {
     openDialog("chrome://calendar/content/calendarCreation.xul", "caEditServer",
                "chrome,titlebar,modal,resizable", aCallback);
 }
--- a/calendar/lightning/content/lightning-utils.js
+++ b/calendar/lightning/content/lightning-utils.js
@@ -33,16 +33,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
+Components.utils.import("resource:///modules/iteratorUtils.jsm");
 Components.utils.import("resource://calendar/modules/calUtils.jsm");
 
 /**
  * Gets the value of a string in a .properties file from the lightning bundle
  *
  * @param aBundleName  the name of the properties file.  It is assumed that the
  *                     file lives in chrome://lightning/locale/
  * @param aStringName  the name of the string within the properties file
@@ -77,20 +78,23 @@ function ltnInitMailIdentitiesRow() {
     // Remove all children from the email list to avoid duplicates if the list
     // has already been populated during a previous step in the calendar
     // creation wizard.
     while (menuPopup.lastChild) {
         menuPopup.removeChild(menuPopup.lastChild);
     }
 
     addMenuItem(menuPopup, ltnGetString("lightning", "imipNoIdentity"), "none");
-    var identities = getAccountManager().allIdentities;
-    for (var i = 0; i <  identities.Count(); ++i) {
-        var identity = identities.GetElementAt(i)
-                                 .QueryInterface(Components.interfaces.nsIMsgIdentity);
+    let identities;
+    if (gCalendar && gCalendar.aclEntry && gCalendar.aclEntry.hasAccessControl) {
+        identities = gCalendar.aclEntry.getOwnerIdentities({});
+    } else {
+        identities = cal.getAccountManager().allIdentities;
+    }
+    for each (let identity in fixIterator(identities, Components.interfaces.nsIMsgIdentity)) {
         addMenuItem(menuPopup, identity.identityName, identity.key);
     }
     try {
         var sel = gCalendar.getProperty("imip.identity");
         if (sel) {
             sel = sel.QueryInterface(Components.interfaces.nsIMsgIdentity);
         }
         menuListSelectItem("email-identity-menulist", sel ? sel.key : "none");
--- a/calendar/providers/caldav/calDavCalendar.js
+++ b/calendar/providers/caldav/calDavCalendar.js
@@ -80,16 +80,17 @@ function calDavCalendar() {
     this.mFirstRefreshDone = false;
     this.mOfflineStorage = null;
     this.mQueuedQueries = [];
     this.mCtag = null;
 
     // By default, support both events and todos.
     this.mGenerallySupportedItemTypes = ["VEVENT", "VTODO"];
     this.mSupportedItemTypes = this.mGenerallySupportedItemTypes.slice(0);
+    this.mACLProperties = {};
 }
 
 // some shorthand
 const calICalendar = Components.interfaces.calICalendar;
 const calIErrors = Components.interfaces.calIErrors;
 const calIFreeBusyInterval = Components.interfaces.calIFreeBusyInterval;
 const calICalDavCalendar = Components.interfaces.calICalDavCalendar;
 
@@ -444,16 +445,20 @@ calDavCalendar.prototype = {
             return this.mItemInfoCache[aItem.id].locationPath;
         } else {
             // New items just use id.ics
             return aItem.id + ".ics";
         }
     },
 
     getProperty: function caldav_getProperty(aName) {
+        if (aName in this.mACLProperties) {
+            return this.mACLProperties[aName];
+        }
+
         switch (aName) {
             case "organizerId":
                 if (this.calendarUserAddress) {
                     return this.calendarUserAddress;
                 } // else use configured email identity
                 break;
             case "organizerCN":
                 return null; // xxx todo
@@ -1240,17 +1245,47 @@ calDavCalendar.prototype = {
             if (!this.mCheckedServerInfo) {
                 this.mQueuedQueries.push(arguments);
             } else {
                 this.mOfflineStorage.getItems.apply(this.mOfflineStorage, arguments);
             }
         }
     },
 
+    fillACLProperties: function caldav_fillACLProperties() {
+        this.mACLProperties["organizerId"] = this.calendarUserAddress;
+        if (this.mACLEntry && this.mACLEntry.hasAccessControl) {
+            let ownerIdentities = this.mACLEntry.getOwnerIdentities({});
+            if (ownerIdentities.length > 0) {
+                let identity = ownerIdentities[0];
+                this.mACLProperties["organizerId"] = identity.email;
+                this.mACLProperties["organizerCN"] = identity.fullName;
+                this.mACLProperties["imip.identity"] = identity;
+            }
+        }
+    },
+
     safeRefresh: function caldav_safeRefresh(aChangeLogListener) {
+        if (!this.mACLEntry) {
+            let thisCalendar = this;
+            let opListener = {
+                onGetResult: function(calendar, status, itemType, detail, count, items) {
+                    ASSERT(false, "unexpected!");
+                },
+                onOperationComplete: function(opCalendar, opStatus, opType, opId, opDetail) {
+                    thisCalendar.mACLEntry = opDetail;
+                    thisCalendar.fillACLProperties();
+                    thisCalendar.safeRefresh(aChangeLogListener);
+                }
+            };
+
+            this.aclManager.getCalendarEntry(this, opListener);
+            return;
+        }
+
         this.ensureTargetCalendar();
 
         if (this.mAuthScheme == "Digest") {
             // the auth could have timed out and be in need of renegotiation
             // we can't risk several calendars doing this simultaneously so
             // we'll force the renegotiation in a sync query, using OPTIONS to keep
             // it quick
             let headchannel = cal.prepHttpChannel(this.makeUri(), null, null, this);
@@ -2036,16 +2071,21 @@ calDavCalendar.prototype = {
      */
     completeCheckServerInfo: function caldav_completeCheckServerInfo(aChangeLogListener, aError) {
         if (Components.isSuccessCode(aError)) {
             // "undefined" is a successcode, so all is good
             this.saveCalendarProperties();
             this.mCheckedServerInfo = true;
             this.setProperty("currentStatus", Components.results.NS_OK);
 
+            // try to reread the ACLs
+            if (this.mACLEntry) {
+                this.mACLEntry.refresh();
+            }
+
             if (this.isCached) {
                 this.safeRefresh(aChangeLogListener);
             } else {
                 this.refresh();
             }
         } else {
             this.reportDavError(aError);
             if (this.isCached && aChangeLogListener) {
--- a/calendar/providers/storage/calStorageCalendar.js
+++ b/calendar/providers/storage/calStorageCalendar.js
@@ -1031,18 +1031,17 @@ calStorageCalendar.prototype = {
         let this_ = this;
         let opListener = {
             onGetResult: function (calendar, status, itemType, detail, count, items) {
             },
             onOperationComplete: function(calendar, status, opType, id, oldOfflineJournalFlag ) {
                 let newOfflineJournalFlag = cICL.OFFLINE_FLAG_MODIFIED_RECORD;
                 if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD || oldOfflineJournalFlag == cICL.OFFLINE_FLAG_DELETED_RECORD) {
                     // Do nothing since a flag of "created" or "deleted" exists
-                }
-                else {
+                } else {
                     this_.setOfflineJournalFlag(aItem, newOfflineJournalFlag);
                 }
                 this_.notifyOperationComplete(aListener,
                                               Components.results.NS_OK,
                                               Components.interfaces.calIOperationListener.MODIFY,
                                               aItem.id,
                                               aItem);
             }
@@ -1057,18 +1056,17 @@ calStorageCalendar.prototype = {
 
             },
             onOperationComplete: function(calendar, status, opType, id, oldOfflineJournalFlag) {
                 var newOfflineJournalFlag = cICL.OFFLINE_FLAG_DELETED_RECORD;
                 if (oldOfflineJournalFlag) {
                     // Delete item if flag is c
                     if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_CREATED_RECORD) {
                         this_.deleteItemById(aItem.id);
-                    }
-                    else if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) {
+                    } else if (oldOfflineJournalFlag == cICL.OFFLINE_FLAG_MODIFIED_RECORD) {
                         this_.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
                     }
                 } else {
                     this_.setOfflineJournalFlag(aItem, cICL.OFFLINE_FLAG_DELETED_RECORD);
                 }
 
                 this_.notifyOperationComplete(aListener,
                                              Components.results.NS_OK,
@@ -1654,19 +1652,19 @@ calStorageCalendar.prototype = {
     // select is executing but this no longer seems to be required.
 
     getAdditionalDataForItem: function cSC_getAdditionalDataForItem(item, flags) {
         // This is needed to keep the modification time intact.
         var savedLastModifiedTime = item.lastModifiedTime;
 
         if (flags & CAL_ITEM_FLAG.HAS_ATTENDEES) {
             var selectItem = null;
-            if (item.recurrenceId == null)
+            if (item.recurrenceId == null) {
                 selectItem = this.mSelectAttendeesForItem;
-            else {
+            } else {
                 selectItem = this.mSelectAttendeesForItemWithRecurrenceId;
                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
             }
 
             try {
                 this.prepareStatement(selectItem);
                 selectItem.params.item_id = item.id;
                 while (selectItem.step()) {
@@ -1683,19 +1681,19 @@ calStorageCalendar.prototype = {
             } finally {
                 selectItem.reset();
             }
         }
 
         var row;
         if (flags & CAL_ITEM_FLAG.HAS_PROPERTIES) {
             var selectItem = null;
-            if (item.recurrenceId == null)
+            if (item.recurrenceId == null) {
                 selectItem = this.mSelectPropertiesForItem;
-            else {
+            } else {
                 selectItem = this.mSelectPropertiesForItemWithRecurrenceId;
                 this.setDateParamHelper(selectItem.params, "recurrence_id", item.recurrenceId);
             }
 
             try {
                 this.prepareStatement(selectItem);
                 selectItem.params.item_id = item.id;
                 while (selectItem.step()) {
@@ -1759,20 +1757,21 @@ calStorageCalendar.prototype = {
 
                         ritem.type = row.recur_type;
                         if (row.count) {
                             try {
                                 ritem.count = row.count;
                             } catch (exc) {
                             }
                         } else {
-                            if (row.end_date)
+                            if (row.end_date) {
                                 ritem.untilDate = newDateTime(row.end_date, "UTC");
-                            else
+                            } else {
                                 ritem.untilDate = null;
+                            }
                         }
                         try {
                             ritem.interval = row.interval;
                         } catch (exc) {
                         }
 
                         var rtypes = ["second",
                                       "minute",
@@ -2306,20 +2305,21 @@ calStorageCalendar.prototype = {
                             datestr += dateToText(getInUtcOrKeepFloating(rdates[j]));
                         }
 
                         ap.dates = datestr;
 
                     } else if (calInstanceOf(ritem, Components.interfaces.calIRecurrenceRule)) {
                         ap.recur_type = ritem.type;
 
-                        if (ritem.isByCount)
+                        if (ritem.isByCount) {
                             ap.count = ritem.count;
-                        else
+                        } else {
                             ap.end_date = ritem.untilDate ? ritem.untilDate.nativeTime : null;
+                        }
 
                         ap.interval = ritem.interval;
 
                         var rtypes = ["second",
                                       "minute",
                                       "hour",
                                       "day",
                                       "monthday",