From f7c44863709a74b070407f6259e622bfdd8c08b2 Mon Sep 17 00:00:00 2001 From: Ludovic Marcotte Date: Mon, 21 Nov 2016 10:45:27 -0500 Subject: [PATCH] (feat) relaxed permission requirements for subscription synchronizations (fixes #3118 and #3180) --- ActiveSync/SOGoActiveSyncDispatcher+Sync.m | 239 +++++++++++++++++---- ActiveSync/SOGoActiveSyncDispatcher.m | 215 +++++++++--------- NEWS | 1 + 3 files changed, 313 insertions(+), 142 deletions(-) diff --git a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m index 59eb7de1a..8ce213122 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher+Sync.m +++ b/ActiveSync/SOGoActiveSyncDispatcher+Sync.m @@ -51,6 +51,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -272,7 +273,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache, *allValues; NSString *clientId, *serverId, *easId; - NSArray *additions; + NSArray *additions, *roles; id anAddition, sogoObject, o; BOOL is_new; @@ -296,6 +297,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. serverId = [NSString stringWithFormat: @"%@.vcf", [theCollection globallyUniqueObjectId]]; sogoObject = [[SOGoContactGCSEntry alloc] initWithName: serverId inContainer: theCollection]; + [sogoObject autorelease]; o = [sogoObject vCard]; } break; @@ -319,6 +321,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { sogoObject = [[SOGoAppointmentObject alloc] initWithName: [serverId sanitizedServerIdWithType: theFolderType] inContainer: theCollection]; + [sogoObject autorelease]; o = [sogoObject component: YES secure: NO]; } else @@ -333,6 +336,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. serverId = [NSString stringWithFormat: @"%@.ics", [theCollection globallyUniqueObjectId]]; sogoObject = [[SOGoTaskObject alloc] initWithName: serverId inContainer: theCollection]; + [sogoObject autorelease]; o = [sogoObject component: YES secure: NO]; } break; @@ -344,11 +348,28 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. abort(); } } - - [o takeActiveSyncValues: allValues inContext: context]; - [sogoObject setIsNew: is_new]; - [sogoObject saveComponent: o]; - + + roles = [theCollection aclsForUser: [[context activeUser] login]]; + + if (![roles containsObject: SOGoRole_ObjectCreator] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + // This will trigger a delete-command to remove the component without proper permission. + // FIXME: ultimately, we need to find a better way to create a phantom object in the user's calendar + // and delete it to keep the transactional process in shape. We could also create a deleted one right way + // We strip any attendees from the values as we don't want to send bogus invitation emails + [allValues removeObjectForKey: @"Attendees"]; + [o takeActiveSyncValues: allValues inContext: context]; + [sogoObject setIsNew: YES]; + [sogoObject saveComponent: o]; + [sogoObject delete]; + } + else + { + [o takeActiveSyncValues: allValues inContext: context]; + [sogoObject setIsNew: is_new]; + [sogoObject saveComponent: o]; + } + // Update syncCache folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; @@ -388,6 +409,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; [dateCache setObject: [NSCalendarDate date] forKey: serverId]; + // make sure to pickup the delete immediately if we don't have proper permission to add + if (![roles containsObject: SOGoRole_ObjectCreator] && ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) + [folderMetadata setObject: [NSNumber numberWithBool: YES] forKey: @"MoreAvailable"]; + [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; // Everything is fine, lets generate our response @@ -444,7 +469,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { NSDictionary *allChanges; NSString *serverId, *easId, *origServerId, *mergedFolder; - NSArray *changes, *a; + NSArray *changes, *a, *roles; id aChange, o, sogoObject; NSMutableDictionary *folderMetadata, *syncCache, *uidCache; @@ -495,7 +520,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { if (debugOn) [self logWithFormat: @"EAS - Found serverId: %@ for easId: %@", serverId, easId]; - } else serverId = easId; @@ -519,32 +543,60 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. { case ActiveSyncContactFolder: { - o = [sogoObject vCard]; - [o takeActiveSyncValues: allChanges inContext: context]; - [sogoObject saveComponent: o]; + roles = [sogoObject aclsForUser:[[context activeUser] login]]; + o = [sogoObject vCard]; - if ([syncCache objectForKey: serverId]) - [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; + // Don't update the component without proper permission + if (([roles containsObject: SOGoRole_ObjectEditor] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])) + { + [o takeActiveSyncValues: allChanges inContext: context]; + [sogoObject saveComponent: o]; + + if ([syncCache objectForKey: serverId]) + [syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; + } + // Trigger a change-command to override client changes since we don't have permissions + else + [sogoObject touch]; } break; case ActiveSyncEventFolder: case ActiveSyncTaskFolder: { + roles = [sogoObject aclsForUser:[[context activeUser] login]]; + o = [sogoObject component: NO secure: NO]; if (theFolderType == ActiveSyncEventFolder && [(iCalEvent *)o userIsAttendee: [context activeUser]]) { - [o changeParticipationStatus: allChanges inContext: context component: sogoObject]; + // Don't update the component without proper permission + if ([roles containsObject: SOGoCalendarRole_ComponentResponder] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + [o changeParticipationStatus: allChanges inContext: context component: sogoObject]; + + if ([syncCache objectForKey: serverId]) + [syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; + } + // Trigger a change-command to override client changes since we don't have permissions + else + [sogoObject touch]; } else { - [o takeActiveSyncValues: allChanges inContext: context]; - [sogoObject saveComponent: o]; - } + // Don't update the component without proper permission + if ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + [o takeActiveSyncValues: allChanges inContext: context]; + [sogoObject saveComponent: o]; - if ([syncCache objectForKey: serverId]) - [syncCache setObject: [NSString stringWithFormat:@"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; + if ([syncCache objectForKey: serverId]) + [syncCache setObject: [NSString stringWithFormat: @"%f", [[sogoObject lastModified] timeIntervalSince1970]] forKey: serverId]; + } + // Trigger a change-command to override client changes since we don't have permissions + else + [sogoObject touch]; + } } break; case ActiveSyncMailFolder: @@ -559,13 +611,12 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. modseq = [[[result objectForKey: @"RawResponse"] objectForKey: @"fetch"] objectForKey: @"modseq"]; if (modseq && [syncCache objectForKey: serverId]) - [syncCache setObject: [modseq stringValue] forKey: serverId]; + [syncCache setObject: [modseq stringValue] forKey: serverId]; } } [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; - [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", origServerId]; [theBuffer appendFormat: @"%d", 1]; @@ -604,10 +655,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType inBuffer: (NSMutableString *) theBuffer { - - id aDelete, sogoObject, value; - NSArray *deletions, *a; NSString *serverId, *easId, *origServerId, *mergedFolder; + NSArray *deletions, *a, *roles; + id aDelete, sogoObject, value; BOOL deletesAsMoves, useTrash; int i; @@ -616,11 +666,9 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if ([deletions count]) { - NSMutableDictionary *folderMetadata, *dateCache, *syncCache, *uidCache; + NSMutableDictionary *folderMetadata, *uidCache; folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; - syncCache = [folderMetadata objectForKey: @"SyncCache"]; - dateCache = [folderMetadata objectForKey: @"DateCache"]; uidCache = [folderMetadata objectForKey: @"UidCache"]; // From the documention, if DeletesAsMoves is missing, we must assume it's a YES. @@ -677,22 +725,44 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (![sogoObject isKindOfClass: [NSException class]]) { - // FIXME: handle errors here - if (deletesAsMoves && theFolderType == ActiveSyncMailFolder) - [(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context]; - else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder) - { - [sogoObject prepareDelete]; - [sogoObject delete]; - } - else - [sogoObject delete]; + // Remove the cache entry. If the delete fails due to permission issues we do a fake update to trigger + // an add-command. + NSMutableDictionary *folderMetadata, *dateCache, *syncCache; + folderMetadata = [self _folderMetadataForKey: [self _getNameInCache: theCollection withType: theFolderType]]; + + syncCache = [folderMetadata objectForKey: @"SyncCache"]; + dateCache = [folderMetadata objectForKey: @"DateCache"]; [syncCache removeObjectForKey: serverId]; [dateCache removeObjectForKey: serverId]; - //[uidCache removeObjectForKey: serverId]; + [self _setFolderMetadata: folderMetadata forKey: [self _getNameInCache: theCollection withType: theFolderType]]; + // FIXME: handle errors here + if (deletesAsMoves && theFolderType == ActiveSyncMailFolder) + { + [(SOGoMailFolder *)[sogoObject container] deleteUIDs: [NSArray arrayWithObjects: serverId, nil] useTrashFolder: &useTrash inContext: context]; + } + else if (theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder || theFolderType == ActiveSyncContactFolder) + { + roles = [theCollection aclsForUser:[[context activeUser] login]]; + + // We check ACLs on the collection and not on the SOGo object itself, as the add/delete rights are on the collection itself + if (![roles containsObject: SOGoRole_ObjectEraser] || ![[sogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + // This will trigger an add-command to re-add the component to the client + [sogoObject touch]; + } + else + { + if (!(theFolderType == ActiveSyncContactFolder)) + [sogoObject prepareDelete]; + [sogoObject delete]; + } + } + else + [sogoObject delete]; + [theBuffer appendString: @""]; [theBuffer appendFormat: @"%@", origServerId]; [theBuffer appendFormat: @"%d", 1]; @@ -991,7 +1061,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } } } - } + } // if (cleanup_needed) ... return_count = 0; component = nil; @@ -1084,8 +1154,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. continue; } - return_count++; - sogoObject = [theCollection lookupName: [uid sanitizedServerIdWithType: theFolderType] inContext: context acquire: 0]; @@ -1093,10 +1161,34 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (theFolderType == ActiveSyncContactFolder) componentObject = [sogoObject vCard]; else - componentObject = [sogoObject component: NO secure: NO]; + componentObject = [sogoObject component: NO secure: YES]; [syncCache setObject: [component objectForKey: @"c_lastmodified"] forKey: uid]; + // We have got a null componentObject, i.e. we have no permission to view the calendar entry. + // For updated calendar entries we have to remove it from the client and for new entries we just ignore them. + if (!componentObject) + { + if (updated) + { + [s appendString: @""]; + + if (![[theCollection nameInContainer] isEqualToString: @"personal"] && theMergeFolder) + [s appendFormat: @"%@{+}%@", [theCollection nameInContainer], easId]; + else + [s appendFormat: @"%@", easId]; + + [s appendString: @""]; + + [syncCache removeObjectForKey: uid]; + [dateCache removeObjectForKey: uid]; + return_count++; + } + + DESTROY(pool); + continue; + } + // No need to set dateCache for Contacts if ((theFolderType == ActiveSyncEventFolder || theFolderType == ActiveSyncTaskFolder)) { @@ -1703,6 +1795,31 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. first_sync = NO; + // We check if the folder permissions have changed since the last sync. If so, we simply clear the cached data + // and a "full sync" will be issued by the EAS client as it'll repull all elements from the collection. + // We don't need to check for contact folder. Either it has View_object or it will be removed by FolderSync. + if ((folderType == ActiveSyncEventFolder || folderType == ActiveSyncTaskFolder) && ![[collection ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + NSArray *folderRoles, *folderRolesInCache; + + folderRoles = [collection aclsForUser: [[context activeUser] login]]; + folderRolesInCache = [folderMetadata objectForKey: @"FolderPermissions"]; + if (![folderRoles isEqualToArray: folderRolesInCache]) + { + if (debugOn) + [self logWithFormat: @"EAS - Folder permission change: %@ (%@ <> %@). Refresh folder", [collection nameInContainer], folderRoles, folderRolesInCache]; + + // Cleanup metadata + [folderMetadata removeObjectForKey: @"SyncKey"]; + [folderMetadata removeObjectForKey: @"SyncCache"]; + [folderMetadata removeObjectForKey: @"DateCache"]; + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + + [folderMetadata setObject: folderRoles forKey: @"FolderPermissions"]; + [self _setFolderMetadata: folderMetadata forKey: folderKey]; + } + } + if ([syncKey isEqualToString: @"0"]) { davCollectionTag = @"-1"; @@ -1841,7 +1958,10 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // Windows phones don't like empty Responses tags - such as: . // We only generate this tag when there is a response if (processed && [s length]) - [commandsBuffer appendFormat: @"%@", s]; + { + [commandsBuffer appendFormat: @"%@", s]; + getChanges = NO; + } } @@ -1984,7 +2104,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. if (!(mfCollection && [mfCollection synchronize])) { if (debugOn) - [self logWithFormat: @"EAS - Folder %@ not found. Reset peronal folder to cleanup", folderName]; + [self logWithFormat: @"EAS - Folder %@ not found. Reset personal folder to cleanup", folderName]; davCollectionTag = @"0"; *changeDetected = YES; @@ -2002,6 +2122,35 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. break; } + // Here we don't need to check for contact folder. Either it has View_object or it will be removed by folersync. + if ((folderType == ActiveSyncEventFolder || folderType == ActiveSyncTaskFolder) && ![[mfCollection ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + NSArray *folderRoles, *folderRolesInCache; + + folderRoles = [mfCollection aclsForUser: [[context activeUser] login]]; + folderRolesInCache = [folderMetadata objectForKey: @"FolderPermissions"]; + + if (![folderRoles isEqualToArray: folderRolesInCache]) + { + if (debugOn) + [self logWithFormat: @"EAS - Folder permission change: %@ (%@ <> %@). Refresh folder", [collection nameInContainer], folderRoles, folderRolesInCache]; + + davCollectionTag = @"0"; + *changeDetected = YES; + + status = 3; + // Cleanup metadata + [folderMetadata removeObjectForKey: @"SyncKey"]; + [folderMetadata removeObjectForKey: @"SyncCache"]; + [folderMetadata removeObjectForKey: @"DateCache"]; + [folderMetadata removeObjectForKey: @"MoreAvailable"]; + + [folderMetadata setObject: folderRoles forKey: @"FolderPermissions"]; + [self _setFolderMetadata: folderMetadata forKey: folderKey]; + break; + } + } + if (debugOn) [self logWithFormat: @"EAS - Merging folder %@ into personal folder", folderName]; @@ -2062,7 +2211,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. cacheUpdateNeeded = YES; } } - } // end if + } // if (![changeBuffer length]) ... if (*changeDetected || cacheUpdateNeeded) [self _setFolderMetadata: folderMetadata forKey: folderKey]; diff --git a/ActiveSync/SOGoActiveSyncDispatcher.m b/ActiveSync/SOGoActiveSyncDispatcher.m index 1f995cbb9..2b1b33212 100644 --- a/ActiveSync/SOGoActiveSyncDispatcher.m +++ b/ActiveSync/SOGoActiveSyncDispatcher.m @@ -82,6 +82,7 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #import #import #import +#import #import #import @@ -138,7 +139,7 @@ void handle_eas_terminate(int signum) - (void) _setFolderMetadata: (NSDictionary *) theFolderMetadata forKey: (NSString *) theFolderKey; - (void) _setOrUnsetSyncRequest: (BOOL) set collections: (NSArray *) collections; - +- (NSString *) _getNameInCache: (id) theCollection withType: (SOGoMicrosoftActiveSyncFolderType) theFolderType; @end @@ -773,19 +774,18 @@ void handle_eas_terminate(int signum) SOGoMailAccount *accountFolder; NSMutableString *s, *commands; SOGoUserFolder *userFolder; - SoSecurityManager *sm; + NSArray *allKeys, *roles; SOGoCacheGCSObject *o; - NSArray *allKeys; id currentFolder; NSData *d; int status, command_count, i, type, fi, count; BOOL first_sync; - sm = [SoSecurityManager sharedSecurityManager]; metadata = [self globalMetadataForDevice]; syncKey = [[(id)[theDocumentElement getElementsByTagName: @"SyncKey"] lastObject] textValue]; s = [NSMutableString string]; + personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer]; first_sync = NO; status = 1; @@ -936,15 +936,8 @@ void handle_eas_terminate(int signum) [o save]; } - // Remove the folder from device if it doesn't exist, we don't want to sync it, or it doesn't have the proper permissions - if (!currentFolder || - ![currentFolder synchronize] || - [sm validatePermission: SoPerm_DeleteObjects - onObject: currentFolder - inContext: context] || - [sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: currentFolder - inContext: context]) + // Remove the folder from device if it doesn't exist, or don't want to sync it. + if (!currentFolder || !([currentFolder synchronize])) { // Don't send a delete when MergedFoler is set, we have done it above. // Windows Phones don't like when a -folder is sent twice. @@ -956,6 +949,22 @@ void handle_eas_terminate(int signum) [o destroy]; } + // Remove the folder from device if it is a contact folder and we have no SOGoRole_ObjectViewer. + if ([currentFolder isKindOfClass: [SOGoContactGCSFolder class]] && ![[currentFolder ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + roles = [currentFolder aclsForUser: [[context activeUser] login]]; + if (![roles containsObject: SOGoRole_ObjectViewer]) + { + // Don't send a delete when MergedFoler is set, we have done it above. + // Windows Phones don't like when a -folder is sent twice. + if (![[[o properties] objectForKey: @"MergedFolder"] isEqualToString: @"2"]) + { + [commands appendFormat: @"%@", [cKey stringByEscapingURL] ]; + command_count++; + } + [o destroy]; + } + } } } } @@ -1063,16 +1072,17 @@ void handle_eas_terminate(int signum) command_count++; } - } + } - personalFolderName = [[[context activeUser] personalCalendarFolderInContext: context] nameInContainer]; - folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy]; - [folders autorelease]; + // We get the list of subscribed calendars + folders = [[[[[context activeUser] homeFolderInContext: context] lookupName: @"Calendar" inContext: context acquire: NO] subFolders] mutableCopy]; + [folders autorelease]; - [folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]]; + // We get the list of subscribed address books + [folders addObjectsFromArray: [[[[context activeUser] homeFolderInContext: context] lookupName: @"Contacts" inContext: context acquire: NO] subFolders]]; // We remove all the folders that aren't GCS-ones, that we don't want to synchronize and - // the ones without write/delete permissions. + // contact folder without SOGoRole_ObjectViewer. count = [folders count]-1; for (; count >= 0; count--) { @@ -1084,26 +1094,28 @@ void handle_eas_terminate(int signum) continue; if (![currentFolder isKindOfClass: [SOGoGCSFolder class]] || - ![currentFolder synchronize] || - [sm validatePermission: SoPerm_DeleteObjects - onObject: currentFolder - inContext: context] || - [sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: currentFolder - inContext: context]) + ![currentFolder synchronize]) { [folders removeObjectAtIndex: count]; } + + // Remove the folder from the device if it is a contact folder and we have no SOGoRole_ObjectViewer access right. + if ([currentFolder isKindOfClass: [SOGoContactGCSFolder class]] && ![[currentFolder ownerInContext: context] isEqualToString: [[context activeUser] login]]) + { + roles = [currentFolder aclsForUser: [[context activeUser] login]]; + if (![roles containsObject: SOGoRole_ObjectViewer]) + [folders removeObjectAtIndex: count]; + } } count = [folders count]-1; for (fi = 0; fi <= count ; fi++) { - if ([[folders objectAtIndex:fi] isKindOfClass: [SOGoAppointmentFolder class]]) - name = [NSString stringWithFormat: @"vevent/%@", [[folders objectAtIndex:fi] nameInContainer]]; + if ([[folders objectAtIndex: fi] isKindOfClass: [SOGoAppointmentFolder class]]) + name = [NSString stringWithFormat: @"vevent/%@", [[folders objectAtIndex: fi] nameInContainer]]; else - name = [NSString stringWithFormat: @"vcard/%@", [[folders objectAtIndex:fi] nameInContainer]]; + name = [NSString stringWithFormat: @"vcard/%@", [[folders objectAtIndex: fi] nameInContainer]]; key = [NSString stringWithFormat: @"%@+%@", [context objectForKey: @"DeviceId"], name]; o = [SOGoCacheGCSObject objectWithName: key inContainer: nil]; @@ -1112,9 +1124,9 @@ void handle_eas_terminate(int signum) [o reloadIfNeeded]; // Decide between add and change - if (![[o properties ] objectForKey: @"displayName"] || first_sync) + if (![[o properties ] objectForKey: @"displayName"] || first_sync) operation = @"Add"; - else if (![[[o properties ] objectForKey: @"displayName"] isEqualToString: [[folders objectAtIndex:fi] displayName]]) + else if (![[[o properties ] objectForKey: @"displayName"] isEqualToString: [[folders objectAtIndex:fi] displayName]]) operation = @"Update"; else operation = nil; @@ -1129,7 +1141,7 @@ void handle_eas_terminate(int signum) command_count++; - [[o properties ] setObject: [[folders objectAtIndex:fi] displayName] forKey: @"displayName"]; + [[o properties ] setObject: [[folders objectAtIndex:fi] displayName] forKey: @"displayName"]; [o save]; name = [NSString stringWithFormat: @"vtodo/%@", [[folders objectAtIndex:fi] nameInContainer]]; @@ -1895,7 +1907,6 @@ void handle_eas_terminate(int signum) SOGoMicrosoftActiveSyncFolderType srcFolderType, dstFolderType; id aMoveOperation; NSArray *moveOperations; - SoSecurityManager *sm; NSMutableString *s; NSData *d; int i; @@ -2051,14 +2062,13 @@ void handle_eas_terminate(int signum) // Save dstMessageId in cache - it will help to recover if the request fails before the response can be sent to client [newSuccessfulMoveItemsOps setObject: dstMessageId forKey: srcMessageId]; } - } else { id srcCollection, dstCollection, srcSogoObject, dstSogoObject; - NSArray *elements; + NSArray *elements, *srcObjectRoles, *dstObjectRoles; NSString *newUID, *origSrcMessageId; - NSMutableDictionary *srcUidCache, *dstUidCache, *dstSyncCache; + NSMutableDictionary *srcUidCache, *dstUidCache, *srcSyncCache, *srcDateCache, *dstSyncCache; NSException *ex; unsigned int count, max; @@ -2098,81 +2108,92 @@ void handle_eas_terminate(int signum) inContext: context acquire: NO]; - sm = [SoSecurityManager sharedSecurityManager]; - if (![sm validatePermission: SoPerm_DeleteObjects - onObject: srcCollection - inContext: context] && - ![srcSogoObject isKindOfClass: [NSException class]]) + if (![srcSogoObject isKindOfClass: [NSException class]]) { - if (![sm validatePermission: SoPerm_AddDocumentsImagesAndFiles - onObject: dstCollection - inContext: context]) + newUID = [srcSogoObject globallyUniqueObjectId]; + dstSogoObject = [[SOGoAppointmentObject alloc] initWithName: [newUID sanitizedServerIdWithType: srcFolderType] + inContainer: dstCollection]; + + dstObjectRoles = [dstSogoObject aclsForUser: [[context activeUser] login]]; + srcObjectRoles = [srcSogoObject aclsForUser: [[context activeUser] login]]; + + if (([dstObjectRoles containsObject: SOGoRole_ObjectCreator] || [[dstSogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) && + ([srcObjectRoles containsObject: SOGoRole_ObjectEraser] || [[srcSogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])) { - newUID = [srcSogoObject globallyUniqueObjectId]; - dstSogoObject = [[SOGoAppointmentObject alloc] initWithName: [newUID sanitizedServerIdWithType: srcFolderType] - inContainer: dstCollection]; elements = [[srcSogoObject calendar: NO secure: NO] allObjects]; max = [elements count]; for (count = 0; count < max; count++) [[elements objectAtIndex: count] setUid: newUID]; ex = [dstSogoObject saveCalendar: [srcSogoObject calendar: NO secure: NO]]; - if (!ex) - { - ex = [srcSogoObject delete]; - - if (dstUidCache) - { - [dstUidCache setObject: newUID forKey: newUID]; - - if (debugOn) - [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; - } - - [dstSyncCache setObject: [dstFolderMetadata objectForKey: @"SyncKey"] forKey: newUID]; - - [s appendFormat: @"%@", origSrcMessageId]; - [s appendFormat: @"%@", newUID]; - [s appendFormat: @"%d", 3]; - - // Save dstMessageId in cache - it will help to recover if the request fails before the response can be sent to client - [newSuccessfulMoveItemsOps setObject: newUID forKey: srcMessageId]; - } - else - { - if ([prevSuccessfulMoveItemsOps objectForKey: srcMessageId]) - { - // Move failed but we can recover the dstMessageId from previous request - [s appendFormat: @"%@", origSrcMessageId]; - [s appendFormat: @"%@", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] ]; - [s appendFormat: @"%d", 3]; - [newSuccessfulMoveItemsOps setObject: [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] forKey: srcMessageId]; - - if (dstUidCache) - { - [dstUidCache setObject: newUID forKey: newUID]; - - if (debugOn) - [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; - } - } - else - { - [s appendFormat: @"%@", origSrcMessageId]; - [s appendFormat: @"%d", 1]; - } - } - } + } else { - [s appendFormat: @"%@", origSrcMessageId]; - [s appendFormat: @"%d", 2]; + if (debugOn) + [self logWithFormat: @"EAS - MoveItem failed due to missing permissions: srcMessageId: %@ dstMessageId: %@", srcMessageId, newUID]; + + // Make sure that the entry gets re-added to the source folder. + srcSyncCache = [srcFolderMetadata objectForKey: @"SyncCache"]; + srcDateCache = [srcFolderMetadata objectForKey: @"DateCache"]; + [srcSyncCache removeObjectForKey: srcMessageId]; + [srcDateCache removeObjectForKey: srcMessageId]; + + // Make sure that the entry gets removed from the destination folder. + [dstSyncCache setObject: [dstFolderMetadata objectForKey: @"SyncKey"] forKey: newUID]; + ex = [dstSogoObject saveCalendar: [srcSogoObject calendar: NO secure: YES]]; + ex = [dstSogoObject delete]; } } + + + if (!ex && ![srcSogoObject isKindOfClass: [NSException class]]) + { + if (([dstObjectRoles containsObject: SOGoRole_ObjectCreator] || [[dstSogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]]) && + ([srcObjectRoles containsObject: SOGoRole_ObjectEraser] || [[srcSogoObject ownerInContext: context] isEqualToString: [[context activeUser] login]])) + ex = [srcSogoObject delete]; + else + ex = [srcSogoObject touch]; // make sure to include the object in next sync. + + if (dstUidCache) + { + [dstUidCache setObject: newUID forKey: newUID]; + + if (debugOn) + [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; + } + + [dstSyncCache setObject: [dstFolderMetadata objectForKey: @"SyncKey"] forKey: newUID]; + + [s appendFormat: @"%@", origSrcMessageId]; + [s appendFormat: @"%@", newUID]; + [s appendFormat: @"%d", 3]; + + // Save dstMessageId in cache - it will help to recover if the request fails before the response can be sent to client + [newSuccessfulMoveItemsOps setObject: newUID forKey: srcMessageId]; + } else { - [s appendFormat: @"%@", origSrcMessageId]; - [s appendFormat: @"%d", 1]; + if ([prevSuccessfulMoveItemsOps objectForKey: srcMessageId]) + { + // Move failed but we can recover the dstMessageId from previous request + [s appendFormat: @"%@", origSrcMessageId]; + [s appendFormat: @"%@", [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] ]; + [s appendFormat: @"%d", 3]; + [newSuccessfulMoveItemsOps setObject: [prevSuccessfulMoveItemsOps objectForKey: srcMessageId] forKey: srcMessageId]; + + if (dstUidCache) + { + [dstUidCache setObject: newUID forKey: newUID]; + + if (debugOn) + [self logWithFormat: @"EAS - Saved new easId: %@ for serverId: %@", newUID, newUID]; + } + } + else + { + [s appendFormat: @"%@", origSrcMessageId]; + [s appendFormat: @"%d", 1]; + } } } diff --git a/NEWS b/NEWS index 42e084c84..2bb8d2114 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,7 @@ New features - [core] support repetitive email alarms on tasks and events (#1053) - [web] allow to hide center column on large screens + - [eas] relaxed permission requirements for subscription synchronizations (#3118 and #3180) Enhancements - [core] added sha256-crypt and sha512-crypt password support