From 8ded5a8aaff1e7b4025b6ffe35e3e36d64ebd687 Mon Sep 17 00:00:00 2001 From: Alexandre Cloutier Date: Fri, 4 Jul 2014 09:51:41 -0400 Subject: [PATCH 01/14] Prevent Invitations and whitelist GUI --- .../Appointments/SOGoAppointmentObject.m | 378 ++++++----- UI/PreferencesUI/UIxPreferences.h | 1 + UI/PreferencesUI/UIxPreferences.m | 18 + UI/Templates/ContactsUI/UIxListEditor.wox | 129 ++-- UI/Templates/PreferencesUI/UIxPreferences.wox | 593 ++++++++-------- UI/WebServerResources/SOGoAutoCompletion.js | 641 ++++++++++-------- UI/WebServerResources/UIxPreferences.css | 50 +- UI/WebServerResources/UIxPreferences.js | 309 ++++++--- 8 files changed, 1264 insertions(+), 855 deletions(-) diff --git a/SoObjects/Appointments/SOGoAppointmentObject.m b/SoObjects/Appointments/SOGoAppointmentObject.m index 584c62cc4..16732e7c4 100644 --- a/SoObjects/Appointments/SOGoAppointmentObject.m +++ b/SoObjects/Appointments/SOGoAppointmentObject.m @@ -412,6 +412,73 @@ withType: @"calendar:invitation-update"]; } +// This methods scans the list of attendees. +- (NSException *) _handleAttendeeAvalability: (NSArray *) theAttendees + forEvent: (iCalEvent *) theEvent +{ + iCalPerson *currentAttendee; + NSMutableArray *attendees, *unavailableAttendees; + NSEnumerator *enumerator; + NSString *currentUID, *buffer; + NSMutableString *reason; + NSDictionary *values; + NSMutableDictionary *value; + SOGoUser *user, *currentUser, *ownerUser; + NSException *e; + int count = 0, i = 0; + + // Build a list of the attendees uids without the ressources + attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]]; + unavailableAttendees = [[NSMutableArray alloc] init]; + enumerator = [theAttendees objectEnumerator]; + while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) + { + user = [SOGoUser userWithLogin: currentUID]; + if (![user isResource]) + { + // Check if the user can be invited to an event. + if ([[user userSettings] objectForKey:@"PreventInvitations"]) + { + values = [NSDictionary dictionaryWithObject:[user cn] forKey:@"Cn"]; + [unavailableAttendees addObject:values]; + } + } + } + } + count = [unavailableAttendees count]; + if (count > 0) + { + if (count > 1) + { + reason = [NSMutableString stringWithString:[self labelForKey: @"These persons cannot be invited :"]]; + for (i = 0; i < count; i++) + { + value = [unavailableAttendees objectAtIndex:i]; + [reason appendString:[value keysWithFormat: @"\n %{Cn}"]]; + if (!(i == (count - 1))) + { + [reason appendString:@" &"]; + } + } + } + else + { + value = [unavailableAttendees objectAtIndex:0]; + reason = [self labelForKey: @"This person cannot be invited:"]; + [reason appendString:[value keysWithFormat: @"\n %{Cn}"]]; + } + [unavailableAttendees release]; + return [NSException exceptionWithHTTPStatus:403 reason: reason]; + } + else { + [unavailableAttendees release]; + return nil; + } +} + // // This methods scans the list of attendees. If they are // considered as resource, it checks for conflicting @@ -435,172 +502,172 @@ NSEnumerator *enumerator; NSString *currentUID; SOGoUser *user, *currentUser, *ownerUser; - + // Build a list of the attendees uids attendees = [NSMutableArray arrayWithCapacity: [theAttendees count]]; enumerator = [theAttendees objectEnumerator]; while ((currentAttendee = [enumerator nextObject])) + { + currentUID = [currentAttendee uid]; + if (currentUID) { - currentUID = [currentAttendee uid]; - if (currentUID) - { - [attendees addObject: currentUID]; - } + [attendees addObject: currentUID]; } - + } + // If the active user is not the owner of the calendar, check possible conflict when // the owner is a resource currentUser = [context activeUser]; if (!activeUserIsOwner && ![currentUser isSuperUser]) - { - [attendees addObject: owner]; - } - + { + [attendees addObject: owner]; + } + enumerator = [attendees objectEnumerator]; while ((currentUID = [enumerator nextObject])) - { + { user = [SOGoUser userWithLogin: currentUID]; if ([user isResource]) - { - SOGoAppointmentFolder *folder; - NSCalendarDate *start, *end; - NGCalendarDateRange *range; - NSMutableArray *fbInfo; - NSArray *allOccurences; - - BOOL must_delete; - int i, j; - - // We get the start/end date for our conflict range. If the event to be added is recurring, we - // check for at least a year to start with. - start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; - end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; - - folder = [user personalCalendarFolderInContext: context]; - - // Deny access to the resource if the ACLs don't allow the user - if (![folder aclSQLListingFilter]) - { - NSDictionary *values; - NSString *reason; - - values = [NSDictionary dictionaryWithObjectsAndKeys: - [user cn], @"Cn", - [user systemEmail], @"SystemEmail"]; - reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; - return [NSException exceptionWithHTTPStatus:403 reason: reason]; - } - - fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start - to: end]]; - - // We first remove any occurences in the freebusy that corresponds to the - // current event. We do this to avoid raising a conflict if we move a 1 hour - // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. - // - // We must also check here for repetitive events that don't overlap our event. - // We remove all events that don't overlap. The events here are already - // decomposed. - // - if ([theEvent isRecurrent]) - allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start - endDate: end] - firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate] - endDate: [theEvent endDate]]]; - else - allOccurences = nil; - - for (i = [fbInfo count]-1; i >= 0; i--) - { - range = [NGCalendarDateRange calendarDateRangeWithStartDate: [[fbInfo objectAtIndex: i] objectForKey: @"startDate"] - endDate: [[fbInfo objectAtIndex: i] objectForKey: @"endDate"]]; - - if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) + { + SOGoAppointmentFolder *folder; + NSCalendarDate *start, *end; + NGCalendarDateRange *range; + NSMutableArray *fbInfo; + NSArray *allOccurences; + + BOOL must_delete; + int i, j; + + // We get the start/end date for our conflict range. If the event to be added is recurring, we + // check for at least a year to start with. + start = [[theEvent startDate] dateByAddingYears: 0 months: 0 days: 0 hours: 0 minutes: 0 seconds: 1]; + end = [[theEvent endDate] dateByAddingYears: ([theEvent isRecurrent] ? 1 : 0) months: 0 days: 0 hours: 0 minutes: 0 seconds: -1]; + + folder = [user personalCalendarFolderInContext: context]; + + // Deny access to the resource if the ACLs don't allow the user + if (![folder aclSQLListingFilter]) + { + NSDictionary *values; + NSString *reason; + + values = [NSDictionary dictionaryWithObjectsAndKeys: + [user cn], @"Cn", + [user systemEmail], @"SystemEmail"]; + reason = [values keysWithFormat: [self labelForKey: @"Cannot access resource: \"%{Cn} %{SystemEmail}\""]]; + return [NSException exceptionWithHTTPStatus:403 reason: reason]; + } + + fbInfo = [NSMutableArray arrayWithArray: [folder fetchFreeBusyInfosFrom: start + to: end]]; + + // We first remove any occurences in the freebusy that corresponds to the + // current event. We do this to avoid raising a conflict if we move a 1 hour + // meeting from 12:00-13:00 to 12:15-13:15. We would overlap on ourself otherwise. + // + // We must also check here for repetitive events that don't overlap our event. + // We remove all events that don't overlap. The events here are already + // decomposed. + // + if ([theEvent isRecurrent]) + allOccurences = [theEvent recurrenceRangesWithinCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: start + endDate: end] + firstInstanceCalendarDateRange: [NGCalendarDateRange calendarDateRangeWithStartDate: [theEvent startDate] + endDate: [theEvent endDate]]]; + else + allOccurences = nil; + + for (i = [fbInfo count]-1; i >= 0; i--) + { + range = [NGCalendarDateRange calendarDateRangeWithStartDate: [[fbInfo objectAtIndex: i] objectForKey: @"startDate"] + endDate: [[fbInfo objectAtIndex: i] objectForKey: @"endDate"]]; + + if ([[[fbInfo objectAtIndex: i] objectForKey: @"c_uid"] compare: [theEvent uid]] == NSOrderedSame) { [fbInfo removeObjectAtIndex: i]; continue; } - - // No need to check if the event isn't recurrent here as it's handled correctly - // when we compute the "end" date. - if ([allOccurences count]) + + // No need to check if the event isn't recurrent here as it's handled correctly + // when we compute the "end" date. + if ([allOccurences count]) { must_delete = YES; - + for (j = 0; j < [allOccurences count]; j++) - { - if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]]) - { - must_delete = NO; - break; - } - } + { + if ([range doesIntersectWithDateRange: [allOccurences objectAtIndex: j]]) + { + must_delete = NO; + break; + } + } if (must_delete) - [fbInfo removeObjectAtIndex: i]; + [fbInfo removeObjectAtIndex: i]; } - } - - // Find the attendee associated to the current UID - for (i = 0; i < [theAttendees count]; i++) - { - currentAttendee = [theAttendees objectAtIndex: i]; - if ([[currentAttendee uid] isEqualToString: currentUID]) - break; - else - currentAttendee = nil; - } - - if ([fbInfo count]) - { - // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit - // is imposed) or if numberOfSimultaneousBookings is greater than the number of - // overlapping events - if ([user numberOfSimultaneousBookings] == 0 || - [user numberOfSimultaneousBookings] > [fbInfo count]) - { - if (currentAttendee) - { - [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; - [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; - } - } - else - { - iCalCalendar *calendar; - NSDictionary *values; - NSString *reason; - iCalEvent *event; - - calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]]; - event = [[calendar events] lastObject]; - - values = [NSDictionary dictionaryWithObjectsAndKeys: - [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", - [user cn], @"Cn", - [user systemEmail], @"SystemEmail", - ([event summary] ? [event summary] : @""), @"EventTitle", - [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate", - nil]; - - reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; - - return [NSException exceptionWithHTTPStatus: 403 - reason: reason]; - } - } - else if (currentAttendee) - { - // No conflict, we auto-accept. We do this for resources automatically if no - // double-booking is observed. If it's not the desired behavior, just don't - // set the resource as one! - [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; - [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; - } - } + } + + // Find the attendee associated to the current UID + for (i = 0; i < [theAttendees count]; i++) + { + currentAttendee = [theAttendees objectAtIndex: i]; + if ([[currentAttendee uid] isEqualToString: currentUID]) + break; + else + currentAttendee = nil; + } + + if ([fbInfo count]) + { + // If we always force the auto-accept if numberOfSimultaneousBookings == 0 (ie., no limit + // is imposed) or if numberOfSimultaneousBookings is greater than the number of + // overlapping events + if ([user numberOfSimultaneousBookings] == 0 || + [user numberOfSimultaneousBookings] > [fbInfo count]) + { + if (currentAttendee) + { + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } + } + else + { + iCalCalendar *calendar; + NSDictionary *values; + NSString *reason; + iCalEvent *event; + + calendar = [iCalCalendar parseSingleFromSource: [[fbInfo objectAtIndex: 0] objectForKey: @"c_content"]]; + event = [[calendar events] lastObject]; + + values = [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat: @"%d", [user numberOfSimultaneousBookings]], @"NumberOfSimultaneousBookings", + [user cn], @"Cn", + [user systemEmail], @"SystemEmail", + ([event summary] ? [event summary] : @""), @"EventTitle", + [[fbInfo objectAtIndex: 0] objectForKey: @"startDate"], @"StartDate", + nil]; + + reason = [values keysWithFormat: [self labelForKey: @"Maximum number of simultaneous bookings (%{NumberOfSimultaneousBookings}) reached for resource \"%{Cn} %{SystemEmail}\". The conflicting event is \"%{EventTitle}\", and starts on %{StartDate}."]]; + + return [NSException exceptionWithHTTPStatus: 403 + reason: reason]; + } + } + else if (currentAttendee) + { + // No conflict, we auto-accept. We do this for resources automatically if no + // double-booking is observed. If it's not the desired behavior, just don't + // set the resource as one! + [[currentAttendee attributes] removeObjectForKey: @"RSVP"]; + [currentAttendee setParticipationStatus: iCalPersonPartStatAccepted]; + } } - + } + return nil; } @@ -608,27 +675,29 @@ // // - (NSException *) _handleAddedUsers: (NSArray *) attendees - fromEvent: (iCalEvent *) newEvent + fromEvent: (iCalEvent *) newEvent { iCalPerson *currentAttendee; NSEnumerator *enumerator; NSString *currentUID; NSException *e; - + // We check for conflicts if ((e = [self _handleResourcesConflicts: attendees forEvent: newEvent])) return e; - + if ((e = [self _handleAttendeeAvalability: attendees forEvent: newEvent])) + return e; + enumerator = [attendees objectEnumerator]; while ((currentAttendee = [enumerator nextObject])) - { - currentUID = [currentAttendee uid]; - if (currentUID) - [self _addOrUpdateEvent: newEvent - forUID: currentUID - owner: owner]; - } - + { + currentUID = [currentAttendee uid]; + if (currentUID) + [self _addOrUpdateEvent: newEvent + forUID: currentUID + owner: owner]; + } + return nil; } @@ -709,8 +778,9 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent withType: @"calendar:cancellation"]; } - if ((ex = [self _handleResourcesConflicts: [newEvent attendees] - forEvent: newEvent])) + if ((ex = [self _handleResourcesConflicts: [newEvent attendees] forEvent: newEvent])) + return ex; + if ((ex = [self _handleAttendeeAvalability: [newEvent attendees] forEvent: newEvent])) return ex; addedAttendees = [changes insertedAttendees]; @@ -783,7 +853,7 @@ inRecurrenceExceptionsForEvent: (iCalEvent *) theEvent // +------------> _handleUpdatedEvent:fromOldEvent: ---> _addOrUpdateEvent:forUID:owner: <-----------+ // | | ^ | // v v | | -// _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: | +// _handleRemovedUsers:withRecurrenceId: _handleSequenceUpdateInEvent:ignoringAttendees:fromOldEvent: | // | | // | [DELETEAction:] | // | | {_handleAdded/Updated...}<--+ | diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index bbbb0a2b1..0164d247f 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -33,6 +33,7 @@ { id item; SOGoUser *user; + SOGoUserSettings *us; NGSieveClient *client; // Addressbook diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 0900c4d3a..096ae7b97 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -41,6 +41,7 @@ #import #import #import +#import #import #import #import @@ -638,6 +639,23 @@ static NSArray *reminderValues = nil; return [userDefaults busyOffHours]; } +- (void) setPreventInvitations: (BOOL) preventInvitations +{ + SOGoUserSettings *us; + + us = [user userSettings]; + [us setBool: preventInvitations forKey: @"PreventInvitations"]; + [us synchronize]; +} + +- (BOOL) preventInvitations +{ + SOGoUserSettings *us; + us = [user userSettings]; + + return [[us objectForKey: @"PreventInvitations"] boolValue]; +} + - (NSArray *) firstWeekList { return [NSArray arrayWithObjects: diff --git a/UI/Templates/ContactsUI/UIxListEditor.wox b/UI/Templates/ContactsUI/UIxListEditor.wox index d82e9cc89..4d265c28d 100644 --- a/UI/Templates/ContactsUI/UIxListEditor.wox +++ b/UI/Templates/ContactsUI/UIxListEditor.wox @@ -1,37 +1,42 @@ - - -
-
    -
    -
    -
    - - - - - - - -
    - - +
    + + +
    +
      +
      + +
      + + + + + + + + + + + +
      +
      +
      +
      + + + - + -
      @@ -44,40 +49,40 @@ - +
      - + + -
      -
      -
      + -
      -
      - - +
      +
      + + -
      -
      + + + + + + diff --git a/UI/Templates/PreferencesUI/UIxPreferences.wox b/UI/Templates/PreferencesUI/UIxPreferences.wox index 9cc6675a2..287029d40 100644 --- a/UI/Templates/PreferencesUI/UIxPreferences.wox +++ b/UI/Templates/PreferencesUI/UIxPreferences.wox @@ -11,11 +11,14 @@ title="title" const:popup="YES" const:cssFiles="datepicker.css" - const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js" + const:jsFiles="RowEditionController.js,PasswordPolicy.js,ckeditor/ckeditor.js,datepicker.js, SOGoAutoCompletion.js" > +
      +
        +
        - +
        • -
        • - + +
        • -
        • + label:value="Calendar Options"/> +
        • + label:value="Mail Options"/>
        • + label:value="IMAP Accounts"/>
        • -
          + label:value="Vacation"/>
        • +
        • -
        • -
          + +
        @@ -128,38 +131,38 @@
        + const:id="language" + const:name="language" + string="languageText" + selection="language" + label:noSelectionString="choose" />
        + const:id="timezone" + const:name="timezone" + string="item" selection="userTimeZone" />
        + const:id="shortDateFormat" + const:name="shortDateFormat" + string="itemShortDateFormatText" selection="userShortDateFormat"/>
        + const:id="longDateFormat" + const:name="longDateFormat" + string="itemLongDateFormatText" selection="userLongDateFormat" + />
        + const:id="timeFormat" + const:name="timeFormat" + string="itemTimeFormatText" selection="userTimeFormat" + />
        + const:id="defaultModule" + const:name="defaultModule" + string="itemModuleText" selection="userDefaultModule"/>
        @@ -167,73 +170,103 @@
        + const:id="weekStartDay" + const:name="weekStartDay" + string="itemWeekStartDay" selection="userWeekStartDay"/>
        + const:id="dayStartTime" + const:name="dayStartTime" + string="item" selection="userDayStartTime"/>
        + const:id="dayEndTime" + const:name="dayEndTime" + string="item" selection="userDayEndTime"/>
        -
        +
        -
        -
        -
        -
        -
        -
        -
        -
        -
        -
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        +
        - - -
        + + +
        +
        - + + + + + + + + + + +
        +
        +
        +
        +
        + + + + +
        + + +
        + + + + + + - + + + +
        + +
        + +
        @@ -246,8 +279,8 @@
        - +
        @@ -258,14 +291,14 @@ + > + > @@ -278,135 +311,135 @@
        - +
        -
        -
        -
        -
        -
        -
        -
        + const:name="subscribedFoldersOnly" + const:id="subscribedFoldersOnly" + var:checked="showSubscribedFoldersOnly" /> + +
        +
        +
        +
        +
        +
        + const:id="messageCheck" + const:name="messageCheck" + string="itemMessageCheckText" selection="userMessageCheck"/>
        + const:id="messageForwarding" + const:name="messageForwarding" + string="itemMessageForwardingText" + selection="userMessageForwarding"/>
        + const:id="replyPlacementList" + const:name="replyPlacementList" + string="itemReplyPlacementText" + selection="userReplyPlacement"/>
        + const:id="signaturePlacementList" + const:name="signaturePlacementList" + string="itemSignaturePlacementText" + selection="userSignaturePlacement"/>
        + const:id="composeMessagesType" + const:name="composeMessagesType" + string="itemComposeMessagesText" + selection="userComposeMessagesType"/>
        + const:id="displayRemoteInlineImages" + const:name="displayRemoteInlineImages" + string="itemDisplayRemoteInlineImagesText" + selection="userDisplayRemoteInlineImages"/>
        - +
        • + >
        • + label:value="Labels"/>
        - -
        - - - - + + +
        + +
        + + + + - - - - -
        - -
        - -
        - +
        + +
        + +
        +
        + > + > - + - - + +
        -
        -
        +
        +
        @@ -414,33 +447,33 @@ + const:name="mailLabelsValue" var:value="mailLabelsValue"/>
        - +
        + var:value="mailAccounts"/>
        + >
        @@ -448,15 +481,15 @@
        - -
        + +
        - - - - -
        + + + + +
        @@ -464,11 +497,11 @@
        - + - +
        @@ -481,40 +514,40 @@
        - +

        + label:value="When I receive a request for a return receipt:" + />

        + label:value="Never send a return receipt"/>

        + label:value="Allow return receipts for some messages"/>
        + label:value="If I'm not in the To or Cc of the message:"/>
        - + + label:value="If the sender is outside my domain:"/>
        - + + label:value="In all other cases:"/> +