From 967cc29c4ebf7c4940f8f669e281b1f214e7b46e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Mon, 25 Jan 2016 22:46:44 +0100 Subject: [PATCH 01/19] oc: Query for fmids in behalf of owner's resource Instead of using connected active user because the fmids are related to the root folder (context) owner. This avoids returning back incorrect identifiers which mostly collide with already associated URLs. --- OpenChange/MAPIStoreContext.m | 29 +++++++++++++++++------------ OpenChange/MAPIStoreMapping.m | 4 ++-- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index f4170a6f7..d67205bef 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -494,8 +494,11 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) mappingId = [mapping idFromURL: childURL]; if (mappingId == NSNotFound) { + const char *owner; + [self logWithFormat: @"No id exist yet for '%@', requesting one", childURL]; - ret = mapistore_indexing_get_new_folderID (connInfo->mstore_ctx, &mappingId); + owner = [[userContext username] UTF8String]; + ret = mapistore_indexing_get_new_folderID_as_user (connInfo->mstore_ctx, owner, &mappingId); if (ret == MAPISTORE_SUCCESS) [mapping registerURL: childURL withID: mappingId]; else @@ -544,29 +547,31 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) return newChangeNumbers; } +/* Get new fmids from mapistore_indexing interface using resource's + owner user */ - (NSArray *) getNewFMIDs: (uint64_t) max { - TALLOC_CTX *memCtx; + const char *owner; + enum mapistore_error ret; NSMutableArray *newFMIDs; - uint64_t count; - struct UI8Array_r *numbers; NSString *newNumber; + uint64_t count, newFID; - memCtx = talloc_zero(NULL, TALLOC_CTX); newFMIDs = [NSMutableArray arrayWithCapacity: max]; + /* Get the resource's owner name */ + owner = [[userContext username] UTF8String]; - if (mapistore_indexing_get_new_folderIDs (connInfo->mstore_ctx, - memCtx, max, &numbers) - != MAPISTORE_SUCCESS || numbers->cValues != max) - abort (); for (count = 0; count < max; count++) { - newNumber = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]]; + ret = mapistore_indexing_get_new_folderID_as_user (connInfo->mstore_ctx, owner, &newFID); + if (ret != MAPISTORE_SUCCESS) + [NSException raise: @"MAPIStoreIOException" + format: @"Impossible to get new fmid for %s", owner]; + + newNumber = [NSString stringWithUnsignedLongLong: newFID]; [newFMIDs addObject: newNumber]; } - talloc_free (memCtx); - return newFMIDs; } diff --git a/OpenChange/MAPIStoreMapping.m b/OpenChange/MAPIStoreMapping.m index b7d303ed6..2e2c9a66d 100644 --- a/OpenChange/MAPIStoreMapping.m +++ b/OpenChange/MAPIStoreMapping.m @@ -258,7 +258,7 @@ MAPIStoreMappingKeyFromId (uint64_t idNbr) if (oldURL != NULL) { [self errorWithFormat: - @"url with idNbr already registered: (oldUrl='%@', newUrl='%@', id=x%.16"PRIx64")", + @"url with idNbr already registered: (oldUrl='%@', newUrl='%@', id=0x%.16"PRIx64")", oldURL, urlString, idNbr]; return NO; } @@ -306,7 +306,7 @@ MAPIStoreMappingKeyFromId (uint64_t idNbr) } else [NSException raise: NSInvalidArgumentException - format: @"number of urls and ids do not match"]; + format: @"number of urls (%d) and ids (%d) do not match", max, [idNbrs count]]; } - (void) unregisterURLWithID: (uint64_t) idNbr From e86e3b24817d3cb027f5e0ba969eb90c8a88fab0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Mon, 25 Jan 2016 22:56:47 +0100 Subject: [PATCH 02/19] oc: Get new change numbers using resource's owner Instead of using the connected active user. Although this provides no changes in the result, it could be depending on changes from the backend so it'd better have it accurated to what the OpenChange DB API offers. --- OpenChange/MAPIStoreContext.m | 42 ++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index d67205bef..4aee98acf 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -509,36 +509,56 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) return mappingId; } +/* Get new change number from openchange db interface using + resource's owner user */ - (uint64_t) getNewChangeNumber { + const char *owner; + enum MAPISTATUS retval; uint64_t newVersionNumber; - if (openchangedb_get_new_changeNumber (connInfo->oc_ctx, connInfo->username, &newVersionNumber) - != MAPI_E_SUCCESS) - abort (); + owner = [[userContext username] UTF8String]; + retval = openchangedb_get_new_changeNumber (connInfo->oc_ctx, owner, &newVersionNumber); + if (retval != MAPI_E_SUCCESS) + [NSException raise: @"MAPIStoreIOException" + format: @"Impossible to get new change number for %s: %s", owner, + mapi_get_errstr (retval)]; return newVersionNumber; } +/* Get new change numbers from openchange db interface using + resource's owner user */ - (NSArray *) getNewChangeNumbers: (uint64_t) max { + const char *owner; + enum MAPISTATUS retval; TALLOC_CTX *memCtx; NSMutableArray *newChangeNumbers; uint64_t count; struct UI8Array_r *numbers; NSString *newNumber; - memCtx = talloc_zero(NULL, TALLOC_CTX); - newChangeNumbers = [NSMutableArray arrayWithCapacity: max]; + memCtx = talloc_new (NULL); + if (!memCtx) + [NSException raise: @"MAPIStoreIOException" + format: @"Not enough memory to allocate change numbers"]; + + newChangeNumbers = [NSMutableArray arrayWithCapacity: max]; + owner = [[userContext username] UTF8String]; + + retval = openchangedb_get_new_changeNumbers (connInfo->oc_ctx, memCtx, owner, max, &numbers); + if (retval != MAPI_E_SUCCESS || numbers->cValues != max) + { + talloc_free (memCtx); + [NSException raise: @"MAPIStoreIOException" + format: @"Failing to get %d new change numbers: %s", max, + mapi_get_errstr (retval)]; + } - if (openchangedb_get_new_changeNumbers (connInfo->oc_ctx, - memCtx, connInfo->username, max, &numbers) - != MAPI_E_SUCCESS || numbers->cValues != max) - abort (); for (count = 0; count < max; count++) { - newNumber - = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]]; + newNumber = [NSString stringWithUnsignedLongLong: numbers->lpui8[count]]; [newChangeNumbers addObject: newNumber]; } From c45baad18822f4c18d36becb4d917c102d70c6a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Mon, 25 Jan 2016 22:59:05 +0100 Subject: [PATCH 03/19] oc: Read FAI messages from shared folder must be possible At least, to be able to read a shared folder using Online mode. It requests the folder to know if the message can be read by current active user. --- OpenChange/MAPIStoreFAIMessage.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/OpenChange/MAPIStoreFAIMessage.m b/OpenChange/MAPIStoreFAIMessage.m index bc5608912..1c0f1b9a0 100644 --- a/OpenChange/MAPIStoreFAIMessage.m +++ b/OpenChange/MAPIStoreFAIMessage.m @@ -24,6 +24,7 @@ #import "MAPIStoreActiveTables.h" #import "MAPIStoreContext.h" +#import "MAPIStoreFolder.h" #import "MAPIStoreUserContext.h" #import "NSObject+MAPIStore.h" @@ -69,7 +70,7 @@ - (BOOL) subscriberCanReadMessage { - return NO; + return [(MAPIStoreFolder *)container subscriberCanReadMessages]; } - (BOOL) subscriberCanModifyMessage From 026c37709a771b4909741508bd5380fc6487f9e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Wed, 27 Jan 2016 21:48:40 +0100 Subject: [PATCH 04/19] oc: Mimetise PidTagRights behaviour to PidTagMemberRights As specified by [MS-OXCFOLD] Section 2.2.2.2.2.8 we exclude FreeBusySimple and FreeBusyDetailed right flags. --- OpenChange/MAPIStoreFolder.m | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 6ffd5da00..39ac407be 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -1267,25 +1267,28 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe inMemCtx: (TALLOC_CTX *) memCtx { uint32_t rights = 0; - SOGoUser *ownerUser; - BOOL userIsOwner; + SOGoUser *activeUser, *ownerUser; ownerUser = [[self userContext] sogoUser]; + activeUser = [context activeUser]; - userIsOwner = [[context activeUser] isEqual: ownerUser]; - if (userIsOwner || [self subscriberCanReadMessages]) - rights |= RightsReadItems; - if (userIsOwner || [self subscriberCanCreateMessages]) - rights |= RightsCreateItems; - if (userIsOwner || [self subscriberCanModifyMessages]) - rights |= RightsEditOwn | RightsEditAll; - if (userIsOwner || [self subscriberCanDeleteMessages]) - rights |= RightsDeleteOwn | RightsDeleteAll; - if ((userIsOwner || [self subscriberCanCreateSubFolders]) - && [self supportsSubFolders]) - rights |= RightsCreateSubfolders; - if (userIsOwner) - rights |= RightsFolderOwner | RightsFolderContact; + if ([activeUser isEqual: ownerUser]) + { + rights = RightsReadItems | RightsCreateItems | RightsEditOwn | RightsEditAll + | RightsDeleteOwn | RightsDeleteAll | RightsFolderOwner | RightsFolderContact | RoleNone; + if ([self supportsSubFolders]) + rights |= RightsCreateSubfolders; + } + else + { + NSArray *roles; + + roles = [[self aclFolder] aclsForUser: [activeUser login]]; + rights = [self exchangeRightsForRoles: roles]; + /* FreeBusySimple and FreeBusyDetailed does not apply here + [MS-OXCFOLD] Section 2.2.2.2.2.8 */ + rights &= ~RightsFreeBusySimple & ~RightsFreeBusyDetailed; + } *data = MAPILongValue (memCtx, rights); From 8c8ad301b4e21a158030079f153374a94035392e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Wed, 27 Jan 2016 23:02:10 +0100 Subject: [PATCH 05/19] oc: Check FolderVisible permission to show permission list When the active user is not the owner of the resource as specified by [MS-OXCPERM] Section 3.2.5.1 --- OpenChange/MAPIStoreFolder.m | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 39ac407be..d94166e38 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -1021,9 +1021,10 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe tableType: (enum mapistore_table_type) tableType andHandleId: (uint32_t) handleId { + BOOL access; enum mapistore_error rc = MAPISTORE_SUCCESS; MAPIStoreTable *table; - SOGoUser *ownerUser; + SOGoUser *activeUser, *ownerUser; if (tableType == MAPISTORE_MESSAGE_TABLE) table = [self messageTable]; @@ -1034,8 +1035,19 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe else if (tableType == MAPISTORE_PERMISSIONS_TABLE) { ownerUser = [[self userContext] sogoUser]; - if ([[context activeUser] isEqual: ownerUser]) - table = [self permissionsTable]; + activeUser = [context activeUser]; + access = [activeUser isEqual: ownerUser]; + if (!access) + { + NSArray *roles; + + roles = [[self aclFolder] aclsForUser: [activeUser login]]; + /* Check FolderVisible right to return the table */ + access = ([self exchangeRightsForRoles: roles] & RoleNone) != 0; + } + + if (access) + table = [self permissionsTable]; else rc = MAPISTORE_ERR_DENIED; } From bcf94c5b4322ad50e7c48bce0937527aa04fdfd4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Wed, 27 Jan 2016 23:03:08 +0100 Subject: [PATCH 06/19] oc: Check FolderOwner right to modify permissions As specified by [MS-OXCPERM] Section 3.2.5.2, the ModifyPermissions ROP is only possible to users which have this right. After this changeset, we check the active user can modify permission list. This is a security fix. --- OpenChange/MAPIStoreFolder.m | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index d94166e38..48c762499 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -1641,6 +1641,21 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe NSArray *permissionRoles; BOOL reset, isAdd = NO, isDelete = NO, isModify = NO; SOGoFolder *aclFolder; + SOGoUser *activeUser, *ownerUser; + + /* Check if we have permissions to modify the permissions. + See [MS-OXCPERM] Section 3.2.5.2 for details */ + ownerUser = [[self userContext] sogoUser]; + activeUser = [context activeUser]; + if (![activeUser isEqual: ownerUser]) + { + /* Check if we have FolderOwner right */ + NSArray *roles; + + roles = [[self aclFolder] aclsForUser: [activeUser login]]; + if (([self exchangeRightsForRoles: roles] & RightsFolderOwner) == 0) + return MAPISTORE_ERR_DENIED; + } aclFolder = [self aclFolder]; From dcb1d2f57459d5760a17ad69d12e3fb2af6d1ca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Thu, 28 Jan 2016 15:47:17 +0100 Subject: [PATCH 07/19] oc: API to know if a message can be deleted by a subscriber Instead of asking general container. This gives the possibility to perform the deletion depending on the data from the message, for instance, the user creator. --- OpenChange/MAPIStoreFolder.m | 2 +- OpenChange/MAPIStoreMessage.h | 1 + OpenChange/MAPIStoreMessage.m | 14 +++++++++++--- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 48c762499..0d14550e1 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -597,7 +597,7 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMe if ([[context activeUser] isEqual: ownerUser] || (![message isKindOfClass: MAPIStoreFAIMessageK] - && [self subscriberCanDeleteMessages])) + && ([self subscriberCanDeleteMessages] || [message subscriberCanDeleteMessage]))) { /* we ensure the table caches are loaded so that old and new state can be compared */ diff --git a/OpenChange/MAPIStoreMessage.h b/OpenChange/MAPIStoreMessage.h index fd89bb027..2f059733e 100644 --- a/OpenChange/MAPIStoreMessage.h +++ b/OpenChange/MAPIStoreMessage.h @@ -82,6 +82,7 @@ - (BOOL) subscriberCanReadMessage; - (BOOL) subscriberCanModifyMessage; +- (BOOL) subscriberCanDeleteMessage; @end diff --git a/OpenChange/MAPIStoreMessage.m b/OpenChange/MAPIStoreMessage.m index d3ba9aae7..5f341f600 100644 --- a/OpenChange/MAPIStoreMessage.m +++ b/OpenChange/MAPIStoreMessage.m @@ -547,7 +547,8 @@ rtf2html (NSData *compressedRTF) for (count = 0; count < max; count++) [[containerTables objectAtIndex: count] restrictedChildKeys]; } - + + /* TODO: Check the save process succeeded */ [self save: memCtx]; /* We make sure that any change-related properties are removed from the properties dictionary, to make sure that related methods will be @@ -649,8 +650,7 @@ rtf2html (NSData *compressedRTF) if (userIsOwner || ([self isKindOfClass: MAPIStoreEmbeddedMessageK] && [mainMessage subscriberCanModifyMessage]) - || [(MAPIStoreFolder *) - [mainMessage container] subscriberCanDeleteMessages]) + || [mainMessage subscriberCanDeleteMessage]) access |= 0x04; *data = MAPILongValue (memCtx, access); @@ -987,14 +987,22 @@ rtf2html (NSData *compressedRTF) return activeUserRoles; } +/* Can the current active user read the message? */ - (BOOL) subscriberCanReadMessage { return NO; } +/* Can the current active user modify the message? */ - (BOOL) subscriberCanModifyMessage { return NO; } +/* Can the current active user delete the message? */ +- (BOOL) subscriberCanDeleteMessage +{ + return NO; +} + @end From b92ca092ed92f73f80c23dab08e59716beaeec8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Thu, 28 Jan 2016 15:50:10 +0100 Subject: [PATCH 08/19] oc: Remove specialised PidTagAccess and PidTagAccessLevel As the generic one from MAPIStoreMessage applies here since we use subscriberCan* protocol to know the values from these properties. --- OpenChange/MAPIStoreGCSMessage.m | 66 -------------------------------- 1 file changed, 66 deletions(-) diff --git a/OpenChange/MAPIStoreGCSMessage.m b/OpenChange/MAPIStoreGCSMessage.m index 84976917e..97898ae50 100644 --- a/OpenChange/MAPIStoreGCSMessage.m +++ b/OpenChange/MAPIStoreGCSMessage.m @@ -54,72 +54,6 @@ return [sogoObject lastModified]; } -- (enum mapistore_error) getPidTagAccess: (void **) data // TODO - inMemCtx: (TALLOC_CTX *) memCtx -{ - MAPIStoreContext *context; - WOContext *woContext; - SoSecurityManager *sm; - MAPIStoreUserContext *userContext; - uint32_t access; - - context = [self context]; - userContext = [self userContext]; - if ([[context activeUser] isEqual: [userContext sogoUser]]) - access = 0x03; - else - { - sm = [SoSecurityManager sharedSecurityManager]; - woContext = [userContext woContext]; - - access = 0; - if (![sm validatePermission: SoPerm_ChangeImagesAndFiles - onObject: sogoObject - inContext: woContext]) - access |= 1; - if (![sm validatePermission: SoPerm_AccessContentsInformation - onObject: sogoObject - inContext: woContext]) - access |= 2; - if (![sm validatePermission: SOGoPerm_DeleteObject - onObject: sogoObject - inContext: woContext]) - access |= 4; - } - *data = MAPILongValue (memCtx, access); - - return MAPISTORE_SUCCESS; -} - -- (enum mapistore_error) getPidTagAccessLevel: (void **) data // TODO - inMemCtx: (TALLOC_CTX *) memCtx -{ - MAPIStoreContext *context; - MAPIStoreUserContext *userContext; - WOContext *woContext; - SoSecurityManager *sm; - uint32_t accessLvl; - - context = [self context]; - userContext = [self userContext]; - if ([[context activeUser] isEqual: [userContext sogoUser]]) - accessLvl = 1; - else - { - sm = [SoSecurityManager sharedSecurityManager]; - woContext = [userContext woContext]; - if (![sm validatePermission: SoPerm_ChangeImagesAndFiles - onObject: sogoObject - inContext: woContext]) - accessLvl = 1; - else - accessLvl = 0; - } - *data = MAPILongValue (memCtx, accessLvl); - - return MAPISTORE_SUCCESS; -} - - (enum mapistore_error) getPidTagChangeKey: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { From a9f336c69d3314dbd92ba686423c3973d4afd0f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Thu, 4 Feb 2016 11:31:00 +0100 Subject: [PATCH 09/19] oc: Check permissions on read opening a shared folder This is a security issue that allowed a user to read the number of messages and its subjects when it does not have any permission to read. Now the user cannot see other's folder without asking for me to the owner. --- OpenChange/MAPIStoreContext.m | 12 ++++++++++-- OpenChange/MAPIStoreSOGo.m | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenChange/MAPIStoreContext.m b/OpenChange/MAPIStoreContext.m index 4aee98acf..32a01e147 100644 --- a/OpenChange/MAPIStoreContext.m +++ b/OpenChange/MAPIStoreContext.m @@ -438,8 +438,16 @@ static inline NSURL *CompleteURLFromMapistoreURI (const char *uri) mapiStoreObjectWithSOGoObject: currentFolder inContainer: nil]; [baseFolder setContext: self]; - *folderPtr = baseFolder; - rc = MAPISTORE_SUCCESS; + + if ([[userContext sogoUser] isEqual: activeUser] + || [baseFolder subscriberCanReadMessages]) + { + *folderPtr = baseFolder; + rc = MAPISTORE_SUCCESS; + } + else + rc = MAPISTORE_ERR_DENIED; + } else if ([[userContext sogoUser] isEqual: activeUser]) rc = MAPISTORE_ERR_NOT_FOUND; diff --git a/OpenChange/MAPIStoreSOGo.m b/OpenChange/MAPIStoreSOGo.m index 41c8bf42a..604482ee7 100644 --- a/OpenChange/MAPIStoreSOGo.m +++ b/OpenChange/MAPIStoreSOGo.m @@ -244,8 +244,13 @@ static void mapiapp_cleanup(void) \details Create a connection context to the sogo backend \param mem_ctx pointer to the memory context + \param conn_info pointer to the connection information available for this context + (database connection, connected user, replica server info) + \param indexing pointer to the indexing database connection \param uri pointer to the sogo path \param private_data pointer to the private backend context + + \note the developer must free allocated private_data */ static enum mapistore_error From 76e586deca7f19f4dbcc35876428d5e8a07e6ef7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Thu, 4 Feb 2016 12:19:09 +0100 Subject: [PATCH 10/19] oc: Added custom MAPI permissions This set is only available at sogo-openchange library scope but they are useful to provide all permissions set from any MAPI client --- OpenChange/MAPIStoreFolder.h | 7 +++++++ OpenChange/MAPIStoreFolder.m | 9 +++++++++ 2 files changed, 16 insertions(+) diff --git a/OpenChange/MAPIStoreFolder.h b/OpenChange/MAPIStoreFolder.h index ab0a56da5..d361bc12c 100644 --- a/OpenChange/MAPIStoreFolder.h +++ b/OpenChange/MAPIStoreFolder.h @@ -22,6 +22,7 @@ #define MAPISTOREFOLDER_H #import +#import @class NSArray; @class NSMutableArray; @@ -41,6 +42,12 @@ #import "MAPIStoreSOGoObject.h" +/* Custom MAPI Permissions */ +extern NSString *SOGoOpenChangeRole_EditOwned; +extern NSString *SOGoOpenChangeRole_DeleteOwned; +extern NSString *SOGoOpenChangeRole_FolderContact; +extern NSString *SOGoOpenChangeRole_FolderOwner; + @interface MAPIStoreFolder : MAPIStoreSOGoObject { MAPIStoreContext *context; diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 0d14550e1..82f96bfaf 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -65,6 +65,15 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMessageTableK, MAPIStoreFolderTableK; +/* Custom MAPI Permissions: + + This set have sogo-openchange library scope +*/ +NSString *SOGoOpenChangeRole_EditOwned = @"EditOwned"; +NSString *SOGoOpenChangeRole_DeleteOwned = @"DeleteOwned"; +NSString *SOGoOpenChangeRole_FolderContact = @"FolderContact"; +NSString *SOGoOpenChangeRole_FolderOwner = @"FolderOwner"; + @implementation MAPIStoreFolder + (void) initialize From 1ca1a273d9729edbeb6a76d0ae282c603bffbabb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Sun, 7 Feb 2016 00:38:07 +0100 Subject: [PATCH 11/19] oc-notes: Implement edit own and delete own permissions By storing the PidTagCreatorName on creation and checking when trying to edit or delete an specific message. --- OpenChange/MAPIStoreDBFolder.h | 10 ++++ OpenChange/MAPIStoreDBFolder.m | 24 +++++----- OpenChange/MAPIStoreDBMessage.m | 84 ++++++++++++++++++++++++++++++--- 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/OpenChange/MAPIStoreDBFolder.h b/OpenChange/MAPIStoreDBFolder.h index 2415af934..1da4d1bd9 100644 --- a/OpenChange/MAPIStoreDBFolder.h +++ b/OpenChange/MAPIStoreDBFolder.h @@ -25,6 +25,16 @@ #import "MAPIStoreFolder.h" +extern NSString *MAPIStoreRightReadItems; +extern NSString *MAPIStoreRightCreateItems; +extern NSString *MAPIStoreRightEditOwn; +extern NSString *MAPIStoreRightEditAll; +extern NSString *MAPIStoreRightDeleteOwn; +extern NSString *MAPIStoreRightDeleteAll; +extern NSString *MAPIStoreRightCreateSubfolders; +extern NSString *MAPIStoreRightFolderOwner; +extern NSString *MAPIStoreRightFolderContact; + @interface MAPIStoreDBFolder : MAPIStoreFolder @end diff --git a/OpenChange/MAPIStoreDBFolder.m b/OpenChange/MAPIStoreDBFolder.m index a7c49b72d..6aea4dd9b 100644 --- a/OpenChange/MAPIStoreDBFolder.m +++ b/OpenChange/MAPIStoreDBFolder.m @@ -51,15 +51,15 @@ static Class EOKeyValueQualifierK, SOGoCacheGCSFolderK, MAPIStoreDBFolderK; -static NSString *MAPIStoreRightReadItems = @"RightsReadItems"; -static NSString *MAPIStoreRightCreateItems = @"RightsCreateItems"; -static NSString *MAPIStoreRightEditOwn = @"RightsEditOwn"; -static NSString *MAPIStoreRightEditAll = @"RightsEditAll"; -static NSString *MAPIStoreRightDeleteOwn = @"RightsDeleteOwn"; -static NSString *MAPIStoreRightDeleteAll = @"RightsDeleteAll"; -static NSString *MAPIStoreRightCreateSubfolders = @"RightsCreateSubfolders"; -static NSString *MAPIStoreRightFolderOwner = @"RightsFolderOwner"; -static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; +NSString *MAPIStoreRightReadItems = @"RightsReadItems"; +NSString *MAPIStoreRightCreateItems = @"RightsCreateItems"; +NSString *MAPIStoreRightEditOwn = @"RightsEditOwn"; +NSString *MAPIStoreRightEditAll = @"RightsEditAll"; +NSString *MAPIStoreRightDeleteOwn = @"RightsDeleteOwn"; +NSString *MAPIStoreRightDeleteAll = @"RightsDeleteAll"; +NSString *MAPIStoreRightCreateSubfolders = @"RightsCreateSubfolders"; +NSString *MAPIStoreRightFolderOwner = @"RightsFolderOwner"; +NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; @implementation MAPIStoreDBFolder @@ -355,8 +355,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; - (BOOL) subscriberCanModifyMessages { - return ([self _testRoleForActiveUser: MAPIStoreRightEditAll] - || [self _testRoleForActiveUser: MAPIStoreRightEditOwn]); + return [self _testRoleForActiveUser: MAPIStoreRightEditAll]; } - (BOOL) subscriberCanReadMessages @@ -377,8 +376,7 @@ static NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; - (BOOL) subscriberCanDeleteMessages { - return ([self _testRoleForActiveUser: MAPIStoreRightDeleteAll] - || [self _testRoleForActiveUser: MAPIStoreRightDeleteOwn]); + return [self _testRoleForActiveUser: MAPIStoreRightDeleteAll]; } - (BOOL) subscriberCanCreateSubFolders diff --git a/OpenChange/MAPIStoreDBMessage.m b/OpenChange/MAPIStoreDBMessage.m index a397f7521..ca2b772d0 100644 --- a/OpenChange/MAPIStoreDBMessage.m +++ b/OpenChange/MAPIStoreDBMessage.m @@ -27,6 +27,9 @@ #import #import #import +#import +#import +#import #import "MAPIStoreContext.h" #import "MAPIStorePropertySelectors.h" @@ -346,6 +349,16 @@ /* Update PredecessorChangeList accordingly */ [self _updatePredecessorChangeList]; + if (isNew) + { + NSString *lastModifierName; + + lastModifierName = (NSString *)[properties objectForKey: MAPIPropertyKey (PidTagLastModifierName)]; + if ([lastModifierName length] > 0) + [properties setObject: lastModifierName + forKey: MAPIPropertyKey (PidTagCreatorName)]; + } + // [self logWithFormat: @"Saving %@", [self description]]; // [self logWithFormat: @"%d props in dict", [properties count]]; @@ -364,20 +377,77 @@ return [msgClass isEqualToString: @"IPM.Microsoft.ScheduleData.FreeBusy"]; } -/* TODO: differentiate between the "Own" and "All" cases */ +//----------------------------- +// Permissions +//----------------------------- + - (BOOL) subscriberCanReadMessage { return [(MAPIStoreFolder *) container subscriberCanReadMessages]; - // || [self _messageIsFreeBusy]); +} + +- (SOGoUser *) _ownerUser +{ + NSString *ownerName; + SOGoUser *ownerUser = nil; + + ownerName = [properties objectForKey: MAPIPropertyKey (PidTagCreatorName)]; + if ([ownerName length] > 0) + ownerUser = [SOGoUser userWithLogin: ownerName]; + + return ownerUser; +} + +- (NSArray *) activeUserRoles +{ + /* Override because of this exception: NSInvalidArgumentException, + reason: [SOGoMAPIDBMessage-aclsForUser:] should be overridden by + subclass */ + if (!activeUserRoles) + { + SOGoUser *activeUser; + + activeUser = [[self context] activeUser]; + activeUserRoles = [[container aclFolder] aclsForUser: [activeUser login]]; + [activeUserRoles retain]; + } + + return activeUserRoles; } - (BOOL) subscriberCanModifyMessage { - return ((isNew - && [(MAPIStoreFolder *) container subscriberCanCreateMessages]) - || (!isNew - && [(MAPIStoreFolder *) container subscriberCanModifyMessages])); - // || [self _messageIsFreeBusy]); + BOOL rc; + NSArray *roles; + + roles = [self activeUserRoles]; + + if (isNew) + rc = [(MAPIStoreFolder *) container subscriberCanCreateMessages]; + else + rc = [roles containsObject: MAPIStoreRightEditAll]; + + /* Check if the message is owned and it has permission to edit it */ + if (!rc && [roles containsObject: MAPIStoreRightEditOwn]) + rc = [[[container context] activeUser] isEqual: [self _ownerUser]]; + + return rc; +} + +- (BOOL) subscriberCanDeleteMessage +{ + BOOL rc; + NSArray *roles; + + roles = [self activeUserRoles]; + + rc = [roles containsObject: MAPIStoreRightDeleteAll]; + + /* Check if the message is owned and it has permission to delete it */ + if (!rc && [roles containsObject: MAPIStoreRightDeleteOwn]) + rc = [[[container context] activeUser] isEqual: [self _ownerUser]]; + + return rc; } - (NSDate *) creationTime From 8b45ee886bf43b519688e4235adfb5a11ed68f1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Tue, 9 Feb 2016 16:19:18 +0100 Subject: [PATCH 12/19] oc: Use the same permission set original from MAPIStoreDBFolder To keep backwards compatibility. --- OpenChange/MAPIStoreDBFolder.m | 10 ---------- OpenChange/MAPIStoreFolder.h | 18 +++++++++++++----- OpenChange/MAPIStoreFolder.m | 18 ++++++++++-------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/OpenChange/MAPIStoreDBFolder.m b/OpenChange/MAPIStoreDBFolder.m index 6aea4dd9b..4fc954112 100644 --- a/OpenChange/MAPIStoreDBFolder.m +++ b/OpenChange/MAPIStoreDBFolder.m @@ -51,16 +51,6 @@ static Class EOKeyValueQualifierK, SOGoCacheGCSFolderK, MAPIStoreDBFolderK; -NSString *MAPIStoreRightReadItems = @"RightsReadItems"; -NSString *MAPIStoreRightCreateItems = @"RightsCreateItems"; -NSString *MAPIStoreRightEditOwn = @"RightsEditOwn"; -NSString *MAPIStoreRightEditAll = @"RightsEditAll"; -NSString *MAPIStoreRightDeleteOwn = @"RightsDeleteOwn"; -NSString *MAPIStoreRightDeleteAll = @"RightsDeleteAll"; -NSString *MAPIStoreRightCreateSubfolders = @"RightsCreateSubfolders"; -NSString *MAPIStoreRightFolderOwner = @"RightsFolderOwner"; -NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; - @implementation MAPIStoreDBFolder + (void) initialize diff --git a/OpenChange/MAPIStoreFolder.h b/OpenChange/MAPIStoreFolder.h index d361bc12c..a6a591ec2 100644 --- a/OpenChange/MAPIStoreFolder.h +++ b/OpenChange/MAPIStoreFolder.h @@ -42,11 +42,19 @@ #import "MAPIStoreSOGoObject.h" -/* Custom MAPI Permissions */ -extern NSString *SOGoOpenChangeRole_EditOwned; -extern NSString *SOGoOpenChangeRole_DeleteOwned; -extern NSString *SOGoOpenChangeRole_FolderContact; -extern NSString *SOGoOpenChangeRole_FolderOwner; +/* MAPI Permissions + + This set has only sogo-openchange library scope + */ +extern NSString *MAPIStoreRightReadItems; +extern NSString *MAPIStoreRightCreateItems; +extern NSString *MAPIStoreRightEditOwn; +extern NSString *MAPIStoreRightEditAll; +extern NSString *MAPIStoreRightDeleteOwn; +extern NSString *MAPIStoreRightDeleteAll; +extern NSString *MAPIStoreRightCreateSubfolders; +extern NSString *MAPIStoreRightFolderOwner; +extern NSString *MAPIStoreRightFolderContact; @interface MAPIStoreFolder : MAPIStoreSOGoObject { diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index 82f96bfaf..a1ef53f94 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -65,14 +65,16 @@ Class NSExceptionK, MAPIStoreFAIMessageK, MAPIStoreMessageTableK, MAPIStoreFAIMessageTableK, MAPIStoreFolderTableK; -/* Custom MAPI Permissions: - - This set have sogo-openchange library scope -*/ -NSString *SOGoOpenChangeRole_EditOwned = @"EditOwned"; -NSString *SOGoOpenChangeRole_DeleteOwned = @"DeleteOwned"; -NSString *SOGoOpenChangeRole_FolderContact = @"FolderContact"; -NSString *SOGoOpenChangeRole_FolderOwner = @"FolderOwner"; +/* MAPI permissions */ +NSString *MAPIStoreRightReadItems = @"RightsReadItems"; +NSString *MAPIStoreRightCreateItems = @"RightsCreateItems"; +NSString *MAPIStoreRightEditOwn = @"RightsEditOwn"; +NSString *MAPIStoreRightEditAll = @"RightsEditAll"; +NSString *MAPIStoreRightDeleteOwn = @"RightsDeleteOwn"; +NSString *MAPIStoreRightDeleteAll = @"RightsDeleteAll"; +NSString *MAPIStoreRightCreateSubfolders = @"RightsCreateSubfolders"; +NSString *MAPIStoreRightFolderOwner = @"RightsFolderOwner"; +NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; @implementation MAPIStoreFolder From 12ac1d30def9dcb7b983e8446591740f8e035f9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Tue, 9 Feb 2016 16:23:15 +0100 Subject: [PATCH 13/19] oc: Implement edit own and delete own in GCSFolders And returning back PidTagCreatorName. This is done by checking the owner of the resource if the given permission is restricted to edit/delete own items. This requires a52bc3b to work in calendar folders as it requires to store and retrieve the MAPI custom permissions in the ACL. --- OpenChange/MAPIStoreGCSMessage.h | 7 +++ OpenChange/MAPIStoreGCSMessage.m | 86 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/OpenChange/MAPIStoreGCSMessage.h b/OpenChange/MAPIStoreGCSMessage.h index cac182874..2cfd480d0 100644 --- a/OpenChange/MAPIStoreGCSMessage.h +++ b/OpenChange/MAPIStoreGCSMessage.h @@ -23,6 +23,8 @@ #ifndef MAPISTOREGCSMESSAGE_H #define MAPISTOREGCSMESSAGE_H +#import + #import "MAPIStoreMessage.h" @interface MAPIStoreGCSMessage : MAPIStoreMessage @@ -30,6 +32,11 @@ } /* subclass helpers */ + +/* Return the message original creator */ +- (NSString *) creator; +- (NSString *) owner; +- (SOGoUser *) ownerUser; - (void) updateVersions; @end diff --git a/OpenChange/MAPIStoreGCSMessage.m b/OpenChange/MAPIStoreGCSMessage.m index 97898ae50..e1792fbdf 100644 --- a/OpenChange/MAPIStoreGCSMessage.m +++ b/OpenChange/MAPIStoreGCSMessage.m @@ -20,10 +20,12 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import #import #import +#import #import #import #import @@ -35,6 +37,7 @@ #import "MAPIStoreTypes.h" #import "MAPIStoreUserContext.h" #import "NSData+MAPIStore.h" +#import "NSString+MAPIStore.h" #import "MAPIStoreGCSMessage.h" @@ -54,6 +57,24 @@ return [sogoObject lastModified]; } +- (enum mapistore_error) getPidTagCreatorName: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + enum mapistore_error rc; + NSString *creator; + + creator = [self creator]; + if (creator) + { + *data = [creator asUnicodeInMemCtx: memCtx]; + rc = MAPISTORE_SUCCESS; + } + else + rc = MAPISTORE_ERR_NOT_FOUND; + + return rc; +} + - (enum mapistore_error) getPidTagChangeKey: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { @@ -175,4 +196,69 @@ andPredecessorChangeList: predecessorChangeList]; } +//---------------------- +// Sharing +//---------------------- + +- (NSString *) creator +{ + return [self owner]; +} + +- (NSString *) owner +{ + return [sogoObject ownerInContext: nil]; +} + +- (SOGoUser *) ownerUser +{ + NSString *ownerName; + SOGoUser *owner = nil; + + ownerName = [self owner]; + if ([ownerName length] != 0) + owner = [SOGoUser userWithLogin: ownerName]; + + return owner; +} + +- (BOOL) subscriberCanModifyMessage +{ + BOOL rc; + NSArray *roles; + + roles = [self activeUserRoles]; + + if (isNew) + rc = [roles containsObject: SOGoRole_ObjectCreator]; + else + rc = [roles containsObject: SOGoRole_ObjectEditor]; + + /* Check if the message is owned and it has permission to edit it */ + if (!rc && [roles containsObject: MAPIStoreRightEditOwn]) + rc = [[[container context] activeUser] isEqual: [self ownerUser]]; + + return rc; +} + +- (BOOL) subscriberCanDeleteMessage +{ + BOOL rc; + NSArray *roles; + + roles = [self activeUserRoles]; + rc = [roles containsObject: SOGoRole_ObjectEraser]; + + /* Check if the message is owned and it has permission to delete it */ + if (!rc && [roles containsObject: MAPIStoreRightDeleteOwn]) + { + NSString *currentUser; + + currentUser = [[container context] activeUser]; + rc = [currentUser isEqual: [self ownerUser]]; + } + + return rc; +} + @end From dd32c659f693d3fe4154ad7f6dd2b0ddfed2d87e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Tue, 9 Feb 2016 16:34:04 +0100 Subject: [PATCH 14/19] oc-calendar: Add edit/delete own and Folder Contact/Owner sharing perm By storing these custom MAPI roles in the ACL. The extension field 'X-SOGO-COMPONENT-CREATED-BY' is used to store the event creator when it is done from Outlook. It is the same field SOGo uses when an event is created from a shared folder in the webmail. The creator and the organizer/owner of the event can be different and it can be used from external sources by checking the organizer field. This matches the specification from [MS-OXOCAL] Section 1.1 which defines the organizer as the owner or creator of the event. --- OpenChange/MAPIStoreAppointmentWrapper.h | 3 + OpenChange/MAPIStoreAppointmentWrapper.m | 57 +++++++++++++++++++ OpenChange/MAPIStoreCalendarEmbeddedMessage.m | 1 + OpenChange/MAPIStoreCalendarFolder.m | 44 +++++++++----- OpenChange/MAPIStoreCalendarMessage.m | 25 ++++---- OpenChange/iCalEvent+MAPIStore.h | 1 + OpenChange/iCalEvent+MAPIStore.m | 9 +++ 7 files changed, 111 insertions(+), 29 deletions(-) diff --git a/OpenChange/MAPIStoreAppointmentWrapper.h b/OpenChange/MAPIStoreAppointmentWrapper.h index 998f3e175..ac96dccc2 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.h +++ b/OpenChange/MAPIStoreAppointmentWrapper.h @@ -68,6 +68,9 @@ - (void) fillMessageData: (struct mapistore_message *) dataPtr inMemCtx: (TALLOC_CTX *) memCtx; +- (NSString *) creator; +- (NSString *) owner; + - (enum mapistore_error) getPidTagSenderEmailAddress: (void **) data inMemCtx: (TALLOC_CTX *) memCtx; - (enum mapistore_error) getPidTagSenderAddressType: (void **) data diff --git a/OpenChange/MAPIStoreAppointmentWrapper.m b/OpenChange/MAPIStoreAppointmentWrapper.m index 4b1d901ec..7c16cf6b1 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.m +++ b/OpenChange/MAPIStoreAppointmentWrapper.m @@ -1023,6 +1023,63 @@ static NSCharacterSet *hexCharacterSet = nil; inMemCtx: memCtx]; } +/* creator (only if created from Outlook/SOGo or organizer as fallback */ +- (NSString *) creator +{ + iCalPerson *person; + NSDictionary *contactInfos; + NSString *creator = nil, *email; + SOGoUserManager *mgr; + + creator = [[event uniqueChildWithTag: @"x-sogo-component-created-by"] + flattenedValuesForKey: @""]; + if ([creator length] == 0) + { + person = [event organizer]; + if (person) + { + email = [person rfc822Email]; + if ([email length] > 0) + { + mgr = [SOGoUserManager sharedUserManager]; + contactInfos = [mgr contactInfosForUserWithUIDorEmail: email]; + if (contactInfos) + creator = [contactInfos objectForKey: @"sAMAccountName"]; + } + } + } + return creator; +} + +/* owner is the organizer of the event, if none, try with the creator + who has saved only from Outlook or SOGo */ +- (NSString *) owner +{ + iCalPerson *person; + NSDictionary *contactInfos; + NSString *email, *owner = nil; + SOGoUserManager *mgr; + + person = [event organizer]; + if (person) + { + email = [person rfc822Email]; + if ([email length] > 0) + { + mgr = [SOGoUserManager sharedUserManager]; + contactInfos = [mgr contactInfosForUserWithUIDorEmail: email]; + if (contactInfos) + owner = [contactInfos objectForKey: @"sAMAccountName"]; + } + } + + if (!owner) + owner = [[event uniqueChildWithTag: @"x-sogo-component-created-by"] + flattenedValuesForKey: @""]; + + return owner; +} + /* sender representing */ - (enum mapistore_error) getPidTagSentRepresentingEmailAddress: (void **) data inMemCtx: (TALLOC_CTX *) memCtx diff --git a/OpenChange/MAPIStoreCalendarEmbeddedMessage.m b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m index 6f0c8a776..02a776949 100644 --- a/OpenChange/MAPIStoreCalendarEmbeddedMessage.m +++ b/OpenChange/MAPIStoreCalendarEmbeddedMessage.m @@ -208,6 +208,7 @@ [[container event] updateFromMAPIProperties: properties inUserContext: [self userContext] withActiveUser: activeUser + isNew: NO inMemCtx: memCtx]; } diff --git a/OpenChange/MAPIStoreCalendarFolder.m b/OpenChange/MAPIStoreCalendarFolder.m index 30547cb02..ed949e638 100644 --- a/OpenChange/MAPIStoreCalendarFolder.m +++ b/OpenChange/MAPIStoreCalendarFolder.m @@ -81,11 +81,7 @@ Following rights are not supported by SOGo specifically: - - DeleteOwned : Delete only own objects - - EditOwned : Edit only own objects - CreateSubfolders: No calendar subfolders - - FolderOwner: No sharing folder ownership? - - FolderContact: No support to store this information - FolderVisible: It is inferred by other rights when extracting */ NSMutableArray *roles; @@ -95,26 +91,35 @@ [roles addObject: SOGoRole_ObjectCreator]; if (rights & RightsDeleteAll) [roles addObject: SOGoRole_ObjectEraser]; + if (rights & RightsDeleteOwn) + [roles addObject: MAPIStoreRightDeleteOwn]; + if (rights & RightsEditAll) { [roles addObject: SOGoCalendarRole_PublicModifier]; [roles addObject: SOGoCalendarRole_PrivateModifier]; [roles addObject: SOGoCalendarRole_ConfidentialModifier]; } - else if (rights & RightsReadItems) + if (rights & RightsEditOwn) + [roles addObject: MAPIStoreRightEditOwn]; + + if (rights & RightsReadItems) { [roles addObject: SOGoCalendarRole_PublicViewer]; [roles addObject: SOGoCalendarRole_PrivateViewer]; [roles addObject: SOGoCalendarRole_ConfidentialViewer]; } if (rights & RightsFreeBusySimple) - { - [roles addObject: SOGoCalendarRole_PublicDAndTViewer]; - } + [roles addObject: SOGoCalendarRole_PublicDAndTViewer]; + if (rights & RightsFreeBusyDetailed) - { - [roles addObject: SOGoCalendarRole_ConfidentialDAndTViewer]; - } + [roles addObject: SOGoCalendarRole_ConfidentialDAndTViewer]; + + if (rights & RightsFolderOwner) + [roles addObject: MAPIStoreRightFolderOwner]; + + if (rights & RightsFolderContact) + [roles addObject: MAPIStoreRightFolderContact]; // [self logWithFormat: @"roles for rights %.8x = (%@)", rights, roles]; @@ -136,19 +141,28 @@ else if ([roles containsObject: SOGoCalendarRole_PublicViewer] && [roles containsObject: SOGoCalendarRole_PrivateViewer] && [roles containsObject: SOGoCalendarRole_ConfidentialViewer]) - // We have to set by hand other rights as only the highest role is returned - // See SOGoAppointmentFolder.m:aclsForUser for details - rights |= RightsReadItems | RightsFreeBusySimple | RightsFreeBusyDetailed; + rights |= RightsReadItems; + + if ([roles containsObject: MAPIStoreRightEditOwn]) + rights |= RightsEditOwn; + if ([roles containsObject: MAPIStoreRightDeleteOwn]) + rights |= RightsDeleteOwn; if ([roles containsObject: SOGoCalendarRole_PublicDAndTViewer]) rights |= RightsFreeBusySimple; if ([roles containsObject: SOGoCalendarRole_ConfidentialDAndTViewer]) - rights |= RightsFreeBusyDetailed; + rights |= RightsFreeBusySimple | RightsFreeBusyDetailed; if ((rights & RightsReadItems) != 0 || (rights & RightsCreateItems) != 0 || (rights & RightsDeleteAll) != 0) rights |= RoleNone; /* actually "folder visible" */ + if ([roles containsObject: MAPIStoreRightFolderOwner]) + rights |= RightsFolderOwner | RoleNone; + + if ([roles containsObject: MAPIStoreRightFolderContact]) + rights |= RightsFolderContact; + // [self logWithFormat: @"rights for roles (%@) = %.8x", roles, rights]; return rights; diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index 62a29f118..de1675045 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -528,6 +528,16 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; ASSIGN (sogoObject, newObject); } +- (NSString *) creator +{ + return [[self _appointmentWrapper] creator]; +} + +- (NSString *) owner +{ + return [[self _appointmentWrapper] owner]; +} + - (BOOL) subscriberCanReadMessage { NSArray *roles; @@ -539,20 +549,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; || [self subscriberCanModifyMessage]); } -- (BOOL) subscriberCanModifyMessage -{ - BOOL rc; - NSArray *roles = [self activeUserRoles]; - - if (isNew) - rc = [roles containsObject: SOGoRole_ObjectCreator]; - else - rc = ([roles containsObject: SOGoCalendarRole_ComponentModifier] - || [roles containsObject: SOGoCalendarRole_ComponentResponder]); - - return rc; -} - - (void) _updateAttachedEvents { NSArray *allAttachments; @@ -632,6 +628,7 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; [masterEvent updateFromMAPIProperties: properties inUserContext: [self userContext] withActiveUser: activeUser + isNew: isNew inMemCtx: memCtx]; [self _updateAttachedEvents]; [[self userContext] activate]; diff --git a/OpenChange/iCalEvent+MAPIStore.h b/OpenChange/iCalEvent+MAPIStore.h index 198526687..8d73923e1 100644 --- a/OpenChange/iCalEvent+MAPIStore.h +++ b/OpenChange/iCalEvent+MAPIStore.h @@ -37,6 +37,7 @@ - (void) updateFromMAPIProperties: (NSDictionary *) properties inUserContext: (MAPIStoreUserContext *) userContext withActiveUser: (SOGoUser *) activeUser + isNew: (BOOL) isNew inMemCtx: (TALLOC_CTX *) memCtx; @end diff --git a/OpenChange/iCalEvent+MAPIStore.m b/OpenChange/iCalEvent+MAPIStore.m index 25877029b..1bf0d261a 100644 --- a/OpenChange/iCalEvent+MAPIStore.m +++ b/OpenChange/iCalEvent+MAPIStore.m @@ -247,6 +247,7 @@ - (void) updateFromMAPIProperties: (NSDictionary *) properties inUserContext: (MAPIStoreUserContext *) userContext withActiveUser: (SOGoUser *) activeUser + isNew: (BOOL) isNew inMemCtx: (TALLOC_CTX *) memCtx { BOOL isAllDay; @@ -573,6 +574,14 @@ } } } + // Creator (with sharing purposes) + if (isNew) + { + value = [properties objectForKey: MAPIPropertyKey (PidTagLastModifierName)]; + if (value) + [[self uniqueChildWithTag: @"x-sogo-component-created-by"] setSingleValue: value + forKey: @""]; + } } @end From 9d461d646faec68f0866bc8b459171471782086b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Tue, 9 Feb 2016 23:54:15 +0100 Subject: [PATCH 15/19] oc-contacts: Add edit/delete own and Folder Contact/Owner sharing perm By storing these custom MAPI roles in the ACL. An extension field called 'X-OPENCHANGE-CREATOR' is created in the vcard to validate the creator/owner of the contact in the shared folder. --- OpenChange/MAPIStoreContactsFolder.m | 31 +++++++++++++++++++++- OpenChange/MAPIStoreContactsMessage.m | 37 +++++++++++++++++++-------- 2 files changed, 56 insertions(+), 12 deletions(-) diff --git a/OpenChange/MAPIStoreContactsFolder.m b/OpenChange/MAPIStoreContactsFolder.m index fd6db947a..d55c34a25 100644 --- a/OpenChange/MAPIStoreContactsFolder.m +++ b/OpenChange/MAPIStoreContactsFolder.m @@ -74,18 +74,34 @@ - (NSArray *) rolesForExchangeRights: (uint32_t) rights { + /* Limitations + + Following rights are not supported by SOGo specifically: + + - CreateSubfolders: No contacts subfolders + - FolderVisible: It is inferred by other rights when extracting + */ NSMutableArray *roles; - roles = [NSMutableArray arrayWithCapacity: 6]; + roles = [NSMutableArray arrayWithCapacity: 8]; if (rights & RightsCreateItems) [roles addObject: SOGoRole_ObjectCreator]; if (rights & RightsDeleteAll) [roles addObject: SOGoRole_ObjectEraser]; + if (rights & RightsDeleteOwn) + [roles addObject: MAPIStoreRightDeleteOwn]; if (rights & RightsEditAll) [roles addObject: SOGoRole_ObjectEditor]; + if (rights & RightsEditOwn) + [roles addObject: MAPIStoreRightEditOwn]; if (rights & RightsReadItems) [roles addObject: SOGoRole_ObjectViewer]; + if (rights & RightsFolderOwner) + [roles addObject: MAPIStoreRightFolderOwner]; + if (rights & RightsFolderContact) + [roles addObject: MAPIStoreRightFolderContact]; + return roles; } @@ -95,15 +111,28 @@ if ([roles containsObject: SOGoRole_ObjectCreator]) rights |= RightsCreateItems; + if ([roles containsObject: SOGoRole_ObjectEraser]) rights |= RightsDeleteAll | RightsDeleteOwn; + else if ([roles containsObject: MAPIStoreRightDeleteOwn]) + rights |= RightsDeleteOwn; + if ([roles containsObject: SOGoRole_ObjectEditor]) rights |= RightsEditAll | RightsEditOwn; + else if ([roles containsObject: MAPIStoreRightEditOwn]) + rights |= RightsEditOwn; + if ([roles containsObject: SOGoRole_ObjectViewer]) rights |= RightsReadItems; if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ + if ([roles containsObject: MAPIStoreRightFolderOwner]) + rights |= RightsFolderOwner | RoleNone; + + if ([roles containsObject: MAPIStoreRightFolderContact]) + rights |= RightsFolderContact; + return rights; } diff --git a/OpenChange/MAPIStoreContactsMessage.m b/OpenChange/MAPIStoreContactsMessage.m index 4befdfd5d..344f31c39 100644 --- a/OpenChange/MAPIStoreContactsMessage.m +++ b/OpenChange/MAPIStoreContactsMessage.m @@ -31,9 +31,11 @@ #import #import #import +#import #import #import #import +#import #import #import "MAPIStoreAttachment.h" @@ -1224,23 +1226,28 @@ enum { // [MS-OXOCNTC] 2.2.1.2.11 } // --------------------------------------------------------- +// Permissions +// --------------------------------------------------------- + +- (NSString *) creator +{ + return [[[sogoObject vCard] uniqueChildWithTag: @"x-openchange-creator"] + flattenedValuesForKey: @""]; +} + +- (NSString *) owner +{ + return [self creator]; +} - (BOOL) subscriberCanReadMessage { return [[self activeUserRoles] containsObject: SOGoRole_ObjectViewer]; } -- (BOOL) subscriberCanModifyMessage -{ - NSArray *roles; - - roles = [self activeUserRoles]; - - return ((isNew - && [roles containsObject: SOGoRole_ObjectCreator]) - || (!isNew && [roles containsObject: SOGoRole_ObjectEditor])); -} - +// --------------------------------------------------------- +// Save +// --------------------------------------------------------- - (void) saveDistList:(TALLOC_CTX *) memCtx { [self warnWithFormat: @"IPM.DistList messages are ignored"]; @@ -1584,6 +1591,14 @@ enum { // [MS-OXOCNTC] 2.2.1.2.11 if (value) [newCard setNote: value]; + /* Store the creator name for sharing purposes */ + if (isNew) + { + value = [[[self context] activeUser] login]; + [[newCard uniqueChildWithTag: @"x-openchange-creator"] + setSingleValue: value forKey: @""]; + } + // // we save the new/modified card // From 51408bbde04fb8e77a4446c4a6a07fabcf650d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Wed, 10 Feb 2016 09:48:09 +0100 Subject: [PATCH 16/19] oc-tasks: Add edit/delete own and Folder Contact/Owner sharing perm By storing these custom MAPI roles in the ACL. Take into account that a task folder is shared with a calendar folder with the same name, therefore permissions are shared and overwritten from different Outlook sections. The extension 'X-SOGO-COMPONENT-CREATED-BY' is used to store the task creator in both Outlook and SOGo Webmail. The PidLidTaskOwner is not yet properly managed and we are always returning the folder owner but to effects of sharing that extension is used by now which matches a little more with what the user expects until we fix the task ownership defined in [MS-OXOTASK]. --- OpenChange/MAPIStoreTasksFolder.m | 66 +++++++++++++++++++++++++++++- OpenChange/MAPIStoreTasksMessage.m | 38 +++++++++++++++++ 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/OpenChange/MAPIStoreTasksFolder.m b/OpenChange/MAPIStoreTasksFolder.m index f4f6ac160..4b198d2d1 100644 --- a/OpenChange/MAPIStoreTasksFolder.m +++ b/OpenChange/MAPIStoreTasksFolder.m @@ -75,6 +75,10 @@ return newMessage; } +// -------------------------------------------- +// Permissions and sharing +// -------------------------------------------- + - (NSArray *) rolesForExchangeRights: (uint32_t) rights { NSMutableArray *roles; @@ -82,21 +86,34 @@ roles = [NSMutableArray arrayWithCapacity: 6]; if (rights & RightsCreateItems) [roles addObject: SOGoRole_ObjectCreator]; + if (rights & RightsDeleteAll) [roles addObject: SOGoRole_ObjectEraser]; + if (rights & RightsDeleteOwn) + [roles addObject: MAPIStoreRightDeleteOwn]; + if (rights & RightsEditAll) { [roles addObject: SOGoCalendarRole_PublicModifier]; [roles addObject: SOGoCalendarRole_PrivateModifier]; [roles addObject: SOGoCalendarRole_ConfidentialModifier]; } - else if (rights & RightsReadItems) + if (rights & RightsEditOwn) + [roles addObject: MAPIStoreRightEditOwn]; + + if (rights & RightsReadItems) { [roles addObject: SOGoCalendarRole_PublicViewer]; [roles addObject: SOGoCalendarRole_PrivateViewer]; [roles addObject: SOGoCalendarRole_ConfidentialViewer]; } + if (rights & RightsFolderOwner) + [roles addObject: MAPIStoreRightFolderOwner]; + + if (rights & RightsFolderContact) + [roles addObject: MAPIStoreRightFolderContact]; + return roles; } @@ -116,20 +133,65 @@ && [roles containsObject: SOGoCalendarRole_PrivateViewer] && [roles containsObject: SOGoCalendarRole_ConfidentialViewer]) rights |= RightsReadItems; + + if ([roles containsObject: MAPIStoreRightEditOwn]) + rights |= RightsEditOwn; + if ([roles containsObject: MAPIStoreRightDeleteOwn]) + rights |= RightsDeleteOwn; + if (rights != 0) rights |= RoleNone; /* actually "folder visible" */ + if ([roles containsObject: MAPIStoreRightFolderOwner]) + rights |= RightsFolderOwner | RoleNone; + + if ([roles containsObject: MAPIStoreRightFolderContact]) + rights |= RightsFolderContact; + return rights; } +- (BOOL) subscriberCanModifyMessages +{ + static NSArray *modifierRoles = nil; + + if (!modifierRoles) + modifierRoles = [[NSArray alloc] initWithObjects: + SOGoCalendarRole_PublicModifier, + SOGoCalendarRole_PrivateModifier, + SOGoCalendarRole_ConfidentialModifier, + nil]; + + return ([[self activeUserRoles] firstObjectCommonWithArray: modifierRoles] + != nil); +} + +- (BOOL) subscriberCanReadMessages +{ + static NSArray *viewerRoles = nil; + + if (!viewerRoles) + viewerRoles = [[NSArray alloc] initWithObjects: + SOGoCalendarRole_PublicViewer, + SOGoCalendarRole_PrivateViewer, + SOGoCalendarRole_ConfidentialViewer, + nil]; + + return ([[self activeUserRoles] firstObjectCommonWithArray: viewerRoles] + != nil); +} + - (EOQualifier *) aclQualifier { return [EOQualifier qualifierWithQualifierFormat: [(SOGoAppointmentFolder *) sogoObject aclSQLListingFilter]]; } +// -------------------------------------------- +// Property getters +// -------------------------------------------- - (enum mapistore_error) getPidTagDefaultPostMessageClass: (void **) data - inMemCtx: (TALLOC_CTX *) memCtx + inMemCtx: (TALLOC_CTX *) memCtx { *data = [@"IPM.Task" asUnicodeInMemCtx: memCtx]; diff --git a/OpenChange/MAPIStoreTasksMessage.m b/OpenChange/MAPIStoreTasksMessage.m index 728fdb909..382c61fa0 100644 --- a/OpenChange/MAPIStoreTasksMessage.m +++ b/OpenChange/MAPIStoreTasksMessage.m @@ -154,6 +154,9 @@ return MAPISTORE_SUCCESS; } +//------------------------------------ +// Specific task related properties +//------------------------------------ - (enum mapistore_error) getPidLidTaskComplete: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { @@ -323,6 +326,7 @@ { NSString *owner; + /* FIXME: This is wrong when setting task's request */ owner = [sogoObject ownerInContext: nil]; *data = [owner asUnicodeInMemCtx: memCtx]; @@ -336,6 +340,25 @@ return [self getLongZero: data inMemCtx: memCtx]; } +// ---------------------------------- +// Sharing +// ---------------------------------- +- (NSString *) creator +{ + iCalToDo *task; + + task = [sogoObject component: NO secure: YES]; + return [[task uniqueChildWithTag: @"x-sogo-component-created-by"] + flattenedValuesForKey: @""]; +} + +- (NSString *) owner +{ + /* This is not true but to allow a user edit its own tasks is required. + FIXME: When PidLidTaskOwner getter is properly implemented for Task Requests */ + return [self creator]; +} + - (BOOL) subscriberCanReadMessage { return ([[self activeUserRoles] @@ -354,6 +377,15 @@ rc = ([roles containsObject: SOGoCalendarRole_ComponentModifier] || [roles containsObject: SOGoCalendarRole_ComponentResponder]); + /* Check if the message is owned and it has permission to edit it */ + if (!rc && [roles containsObject: MAPIStoreRightEditOwn]) + { + NSString *currentUser; + + currentUser = [[container context] activeUser]; + rc = [currentUser isEqual: [self ownerUser]]; + } + return rc; } @@ -524,10 +556,16 @@ [vToDo setAccessClass: @"PUBLIC"]; } + /* Creation */ now = [NSCalendarDate date]; if ([sogoObject isNew]) { [vToDo setCreated: now]; + /* Creator is used for sharing purposes */ + value = [properties objectForKey: MAPIPropertyKey (PidTagLastModifierName)]; + if (value) + [[vToDo uniqueChildWithTag: @"x-sogo-component-created-by"] setSingleValue: value + forKey: @""]; } [vToDo setTimeStampAsDate: now]; From 17b2e3946c5e657309f462a2455e3abd5defa16f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Wed, 10 Feb 2016 11:32:28 +0100 Subject: [PATCH 17/19] oc-notes: Point which folder class must be used while opening subfolders It was using MAPIStoreDBFolder class instead of specialised version MAPIStoreNotesFolder and thus the shared subfolders where set to create messages as normal messages instead of notes. --- OpenChange/MAPIStoreNotesContext.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/OpenChange/MAPIStoreNotesContext.m b/OpenChange/MAPIStoreNotesContext.m index 725b0d158..f5b8b1be3 100644 --- a/OpenChange/MAPIStoreNotesContext.m +++ b/OpenChange/MAPIStoreNotesContext.m @@ -1,6 +1,7 @@ /* MAPIStoreNotesContext.m - this file is part of SOGo * * Copyright (C) 2010-2012 Inverse inc. + * Copyright (C) 2016 Enrique J. Hernandez * * Author: Wolfgang Sourdeau * @@ -30,13 +31,30 @@ #undef DEBUG #include +static Class MAPIStoreNotesFolderK; + @implementation MAPIStoreNotesContext ++ (void) initialize +{ + MAPIStoreNotesFolderK = [MAPIStoreNotesFolder class]; +} + + (NSString *) MAPIModuleName { return @"notes"; } ++ (enum mapistore_context_role) MAPIContextRole +{ + return MAPISTORE_NOTES_ROLE; +} + +- (Class) MAPIStoreFolderClass +{ + return MAPIStoreNotesFolderK; +} + + (struct mapistore_contexts_list *) listContextsForUser: (NSString *) userName withIndexing: (struct indexing_context *) indexing inMemCtx: (TALLOC_CTX *) memCtx From 12e952eb5524810a7a3cefa9cbc30f015c1d160d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrique=20J=2E=20Hern=C3=A1ndez=20Blasco?= Date: Mon, 22 Feb 2016 17:51:54 +0100 Subject: [PATCH 18/19] oc: Honor calendar WebMail UI permissions on OpenChange library By expanding roles from the given ACL to have these values as flags inside the OpenChange library. This only applies to Calendar and Tasks folders which stored four different access rights to three different types of events/tasks. As the events and tasks are stored in the same table, I have added two new classes to manage permissions in the same way and this avoids the code duplication called MAPIStoreCalTask(Folder|Message). --- OpenChange/GNUmakefile | 3 + OpenChange/MAPIStoreAppointmentWrapper.h | 1 + OpenChange/MAPIStoreAppointmentWrapper.m | 40 +++++---- OpenChange/MAPIStoreCalTaskFolder.h | 35 ++++++++ OpenChange/MAPIStoreCalTaskFolder.m | 101 +++++++++++++++++++++ OpenChange/MAPIStoreCalTaskMessage.h | 40 +++++++++ OpenChange/MAPIStoreCalTaskMessage.m | 107 +++++++++++++++++++++++ OpenChange/MAPIStoreCalendarFolder.h | 4 +- OpenChange/MAPIStoreCalendarMessage.h | 4 +- OpenChange/MAPIStoreCalendarMessage.m | 16 ++-- OpenChange/MAPIStoreFolder.h | 2 + OpenChange/MAPIStoreFolder.m | 8 ++ OpenChange/MAPIStoreGCSFolder.m | 1 + OpenChange/MAPIStoreMessage.h | 1 + OpenChange/MAPIStoreMessage.m | 10 +++ OpenChange/MAPIStorePermissionsTable.m | 1 + OpenChange/MAPIStoreTasksFolder.h | 4 +- OpenChange/MAPIStoreTasksMessage.h | 4 +- OpenChange/MAPIStoreTasksMessage.m | 65 +++++++------- 19 files changed, 381 insertions(+), 66 deletions(-) create mode 100644 OpenChange/MAPIStoreCalTaskFolder.h create mode 100644 OpenChange/MAPIStoreCalTaskFolder.m create mode 100644 OpenChange/MAPIStoreCalTaskMessage.h create mode 100644 OpenChange/MAPIStoreCalTaskMessage.m diff --git a/OpenChange/GNUmakefile b/OpenChange/GNUmakefile index 4fc207904..739fbd95b 100644 --- a/OpenChange/GNUmakefile +++ b/OpenChange/GNUmakefile @@ -78,6 +78,9 @@ $(SOGOBACKEND)_OBJC_FILES += \ MAPIStoreGCSMessage.m \ MAPIStoreGCSMessageTable.m \ \ + MAPIStoreCalTaskFolder.m \ + MAPIStoreCalTaskMessage.m \ + \ MAPIStoreCalendarAttachment.m \ MAPIStoreCalendarContext.m \ MAPIStoreCalendarFolder.m \ diff --git a/OpenChange/MAPIStoreAppointmentWrapper.h b/OpenChange/MAPIStoreAppointmentWrapper.h index ac96dccc2..5ed4c83ff 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.h +++ b/OpenChange/MAPIStoreAppointmentWrapper.h @@ -70,6 +70,7 @@ - (NSString *) creator; - (NSString *) owner; +- (NSUInteger) sensitivity; - (enum mapistore_error) getPidTagSenderEmailAddress: (void **) data inMemCtx: (TALLOC_CTX *) memCtx; diff --git a/OpenChange/MAPIStoreAppointmentWrapper.m b/OpenChange/MAPIStoreAppointmentWrapper.m index 7c16cf6b1..1881a16a5 100644 --- a/OpenChange/MAPIStoreAppointmentWrapper.m +++ b/OpenChange/MAPIStoreAppointmentWrapper.m @@ -1080,6 +1080,29 @@ static NSCharacterSet *hexCharacterSet = nil; return owner; } +- (NSUInteger) sensitivity +{ + NSString *accessClass = nil; + NSUInteger v; + + accessClass = [event accessClass]; + if (accessClass) + { + if ([accessClass isEqualToString: @"X-PERSONAL"]) + v = 0x1; + else if ([accessClass isEqualToString: @"PRIVATE"]) + v = 0x2; + else if ([accessClass isEqualToString: @"CONFIDENTIAL"]) + v = 0x3; + else + v = 0x0; /* PUBLIC */ + } + else + v = 0x0; /* PUBLIC */ + + return v; +} + /* sender representing */ - (enum mapistore_error) getPidTagSentRepresentingEmailAddress: (void **) data inMemCtx: (TALLOC_CTX *) memCtx @@ -1242,23 +1265,8 @@ static NSCharacterSet *hexCharacterSet = nil; { /* See [MS-OXCICAL] Section 2.1.3.11.20.4 */ uint32_t v; - NSString *accessClass; - - accessClass = [event accessClass]; - if (accessClass) - { - if ([accessClass isEqualToString: @"X-PERSONAL"]) - v = 0x1; - else if ([accessClass isEqualToString: @"PRIVATE"]) - v = 0x2; - else if ([accessClass isEqualToString: @"CONFIDENTIAL"]) - v = 0x3; - else - v = 0x0; /* PUBLIC */ - } - else - v = 0x0; /* PUBLIC */ + v = (uint32_t) [self sensitivity]; *data = MAPILongValue (memCtx, v); return MAPISTORE_SUCCESS; diff --git a/OpenChange/MAPIStoreCalTaskFolder.h b/OpenChange/MAPIStoreCalTaskFolder.h new file mode 100644 index 000000000..92b75e463 --- /dev/null +++ b/OpenChange/MAPIStoreCalTaskFolder.h @@ -0,0 +1,35 @@ +/* MAPIStoreCalTaskFolder.h - this file is part of SOGo + * + * Copyright (C) 2016 Enrique J. Hernandez + * + * Author: Enrique J. Hernandez + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef MAPISTORECALTASKFOLDER_H +#define MAPISTORECALTASKFOLDER_H + +#import "MAPIStoreGCSFolder.h" + +/* This class is intended to share code between Calendar and Tasks as + of nowadays share the table */ + +@interface MAPIStoreCalTaskFolder : MAPIStoreGCSFolder + +@end + +#endif /* MAPISTORECALTASKFOLDER_H */ diff --git a/OpenChange/MAPIStoreCalTaskFolder.m b/OpenChange/MAPIStoreCalTaskFolder.m new file mode 100644 index 000000000..8876c77e5 --- /dev/null +++ b/OpenChange/MAPIStoreCalTaskFolder.m @@ -0,0 +1,101 @@ +/* MAPIStoreCalTaskFolder.m - this file is part of SOGo + * + * Copyright (C) 2016 Enrique J. Hernandez + * + * Author: Enrique J. Hernandez + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#import +#import +#import +#import + +#import + +#import "MAPIStoreCalTaskFolder.h" + +@implementation MAPIStoreCalTaskFolder + +- (NSArray *) expandRoles: (NSArray *) roles +{ + static NSDictionary *rolesMap = nil; + NSArray *subRoles; + NSMutableSet *expandedRoles; + NSString *role, *subRole; + NSUInteger i, max, j; + + if (!rolesMap) + { + /* Build the map of array permissions */ + rolesMap = [[NSDictionary alloc] initWithObjects: [NSArray arrayWithObjects: + [NSArray arrayWithObjects: SOGoCalendarRole_PublicDAndTViewer, + SOGoCalendarRole_PublicViewer, + SOGoCalendarRole_PublicResponder, + SOGoCalendarRole_PublicModifier, nil], + [NSArray arrayWithObjects: SOGoCalendarRole_ConfidentialDAndTViewer, + SOGoCalendarRole_ConfidentialViewer, + SOGoCalendarRole_ConfidentialResponder, + SOGoCalendarRole_ConfidentialModifier, nil], + [NSArray arrayWithObjects: SOGoCalendarRole_PrivateDAndTViewer, + SOGoCalendarRole_PrivateViewer, + SOGoCalendarRole_PrivateResponder, + SOGoCalendarRole_PrivateModifier, nil], + [NSArray arrayWithObjects: SOGoCalendarRole_ComponentDAndTViewer, + SOGoCalendarRole_ComponentViewer, + SOGoCalendarRole_ComponentResponder, + SOGoCalendarRole_ComponentModifier, nil], + nil] + forKeys: [NSArray arrayWithObjects: @"Public", @"Confidential", @"Private", + @"Component", nil]]; + } + + max = [roles count]; + expandedRoles = [NSMutableSet set]; + for (i = 0; i < max; i++) + { + role = [roles objectAtIndex: i]; + subRoles = nil; + if ([role hasPrefix: @"Public"]) + subRoles = [rolesMap objectForKey: @"Public"]; + else if ([role hasPrefix: @"Confidential"]) + subRoles = [rolesMap objectForKey: @"Confidential"]; + else if ([role hasPrefix: @"Private"]) + subRoles = [rolesMap objectForKey: @"Private"]; + else if ([role hasPrefix: @"Component"]) + subRoles = [rolesMap objectForKey: @"Component"]; + + if (subRoles) + { + for (j = 0; j < [subRoles count]; j++) + { + subRole = [subRoles objectAtIndex: j]; + [expandedRoles addObject: subRole]; + + if ([subRole isEqualToString: role]) + break; + } + } + else + { + [expandedRoles addObject: role]; + } + } + + return [expandedRoles allObjects]; +} + +@end diff --git a/OpenChange/MAPIStoreCalTaskMessage.h b/OpenChange/MAPIStoreCalTaskMessage.h new file mode 100644 index 000000000..8e284b414 --- /dev/null +++ b/OpenChange/MAPIStoreCalTaskMessage.h @@ -0,0 +1,40 @@ +/* MAPIStoreCalTaskMessage.h - this file is part of SOGo + * + * Copyright (C) 2016 Enrique J. Hernandez + * + * Author: Enrique J. Hernandez + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef MAPISTORECALTASKMESSAGE_H +#define MAPISTORECALTASKMESSAGE_H + +#import "MAPIStoreGCSMessage.h" + +/* This class is intended to share common logic for Calendar and Tasks + as of today they are stored in the same table. This is relevant for + permissions */ +@interface MAPIStoreCalTaskMessage : MAPIStoreGCSMessage +{ +} + +/* Get the sensitivity (access class) from a message */ +- (NSUInteger) sensitivity; + +@end + +#endif /* MAPISTORECALTASKMESSAGE_H */ diff --git a/OpenChange/MAPIStoreCalTaskMessage.m b/OpenChange/MAPIStoreCalTaskMessage.m new file mode 100644 index 000000000..b88e9c2ef --- /dev/null +++ b/OpenChange/MAPIStoreCalTaskMessage.m @@ -0,0 +1,107 @@ +/* MAPIStoreCalTaskMessage.h - this file is part of SOGo + * + * Copyright (C) 2016 Enrique J. Hernandez + * + * Author: Enrique J. Hernandez + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#import +#import + +#import + +#import + +#import "MAPIStoreFolder.h" +#import "MAPIStoreCalTaskMessage.h" + +@implementation MAPIStoreCalTaskMessage + +/* It must be implemented by subclasses */ +- (NSUInteger) sensitivity +{ + [self subclassResponsibility: _cmd]; + + return 0; +} + +- (NSArray *) expandRoles: (NSArray *) roles +{ + return [container expandRoles: roles]; +} + +- (BOOL) subscriberCanModifyMessage +{ + BOOL rc; + NSArray *roles; + + roles = [self activeUserRoles]; + + if (isNew) + rc = [roles containsObject: SOGoRole_ObjectCreator]; + else + rc = ([roles containsObject: SOGoCalendarRole_ComponentModifier] + || [roles containsObject: SOGoCalendarRole_ComponentResponder]); + + /* Check if the message is owned and it has permission to edit it */ + if (!rc && [roles containsObject: MAPIStoreRightEditOwn]) + { + NSString *currentUser; + + currentUser = [[container context] activeUser]; + rc = [currentUser isEqual: [self ownerUser]]; + } + + if (!rc) + { + NSUInteger sensitivity; + + /* Get sensitivity of the message to check if the user can + modify the message */ + sensitivity = [self sensitivity]; + /* FIXME: Use OpenChange constant names */ + switch (sensitivity) + { + case 0: /* PUBLIC */ + rc = [roles containsObject: SOGoCalendarRole_PublicModifier] + || [roles containsObject: SOGoCalendarRole_PublicResponder]; + break; + case 1: /* PERSONAL */ + case 2: /* PRIVATE */ + rc = [roles containsObject: SOGoCalendarRole_PrivateModifier] + || [roles containsObject: SOGoCalendarRole_PrivateResponder]; + break; + case 3: /* CONFIDENTIAL */ + rc = [roles containsObject: SOGoCalendarRole_ConfidentialModifier] + || [roles containsObject: SOGoCalendarRole_ConfidentialResponder]; + } + } + return rc; +} + +- (BOOL) subscriberCanReadMessage +{ + NSArray *roles; + + roles = [self activeUserRoles]; + + return ([roles containsObject: SOGoCalendarRole_ComponentViewer] + || [roles containsObject: SOGoCalendarRole_ComponentDAndTViewer] + || [self subscriberCanModifyMessage]); +} + +@end diff --git a/OpenChange/MAPIStoreCalendarFolder.h b/OpenChange/MAPIStoreCalendarFolder.h index fd6c5d20f..9b3a4cf5c 100644 --- a/OpenChange/MAPIStoreCalendarFolder.h +++ b/OpenChange/MAPIStoreCalendarFolder.h @@ -23,9 +23,9 @@ #ifndef MAPISTORECALENDARFOLDER_H #define MAPISTORECALENDARFOLDER_H -#import "MAPIStoreGCSFolder.h" +#import "MAPIStoreCalTaskFolder.h" -@interface MAPIStoreCalendarFolder : MAPIStoreGCSFolder +@interface MAPIStoreCalendarFolder : MAPIStoreCalTaskFolder @end diff --git a/OpenChange/MAPIStoreCalendarMessage.h b/OpenChange/MAPIStoreCalendarMessage.h index 8b2de759d..99e286073 100644 --- a/OpenChange/MAPIStoreCalendarMessage.h +++ b/OpenChange/MAPIStoreCalendarMessage.h @@ -23,13 +23,13 @@ #ifndef MAPISTORECALENDARMESSAGE_H #define MAPISTORECALENDARMESSAGE_H -#import "MAPIStoreGCSMessage.h" +#import "MAPIStoreCalTaskMessage.h" @class iCalCalendar; @class iCalEvent; @class MAPIStoreAppointmentWrapper; -@interface MAPIStoreCalendarMessage : MAPIStoreGCSMessage +@interface MAPIStoreCalendarMessage : MAPIStoreCalTaskMessage { iCalCalendar *calendar; iCalEvent *masterEvent; diff --git a/OpenChange/MAPIStoreCalendarMessage.m b/OpenChange/MAPIStoreCalendarMessage.m index de1675045..5ec92d07f 100644 --- a/OpenChange/MAPIStoreCalendarMessage.m +++ b/OpenChange/MAPIStoreCalendarMessage.m @@ -528,6 +528,11 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; ASSIGN (sogoObject, newObject); } +- (NSUInteger) sensitivity +{ + return [[self _appointmentWrapper] sensitivity]; +} + - (NSString *) creator { return [[self _appointmentWrapper] creator]; @@ -538,17 +543,6 @@ static Class NSArrayK, MAPIStoreAppointmentWrapperK; return [[self _appointmentWrapper] owner]; } -- (BOOL) subscriberCanReadMessage -{ - NSArray *roles; - - roles = [self activeUserRoles]; - - return ([roles containsObject: SOGoCalendarRole_ComponentViewer] - || [roles containsObject: SOGoCalendarRole_ComponentDAndTViewer] - || [self subscriberCanModifyMessage]); -} - - (void) _updateAttachedEvents { NSArray *allAttachments; diff --git a/OpenChange/MAPIStoreFolder.h b/OpenChange/MAPIStoreFolder.h index a6a591ec2..46e13d235 100644 --- a/OpenChange/MAPIStoreFolder.h +++ b/OpenChange/MAPIStoreFolder.h @@ -89,6 +89,8 @@ extern NSString *MAPIStoreRightFolderContact; - (MAPIStorePermissionsTable *) permissionsTable; - (NSArray *) permissionEntries; +- (NSArray *) expandRoles: (NSArray *) roles; + /* message objects and tables */ - (id) lookupMessage: (NSString *) messageKey; - (NSArray *) messageKeys; diff --git a/OpenChange/MAPIStoreFolder.m b/OpenChange/MAPIStoreFolder.m index a1ef53f94..43efcaff9 100644 --- a/OpenChange/MAPIStoreFolder.m +++ b/OpenChange/MAPIStoreFolder.m @@ -919,6 +919,11 @@ NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; return nil; } +- (NSArray *) expandRoles: (NSArray *) roles +{ + return roles; +} + - (void) _modifyPermissionEntryForUser: (NSString *) user withRoles: (NSArray *) roles isAddition: (BOOL) isAddition @@ -1053,6 +1058,7 @@ NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; NSArray *roles; roles = [[self aclFolder] aclsForUser: [activeUser login]]; + roles = [self expandRoles: roles]; // Not required here /* Check FolderVisible right to return the table */ access = ([self exchangeRightsForRoles: roles] & RoleNone) != 0; } @@ -1307,6 +1313,7 @@ NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; NSArray *roles; roles = [[self aclFolder] aclsForUser: [activeUser login]]; + roles = [self expandRoles: roles]; rights = [self exchangeRightsForRoles: roles]; /* FreeBusySimple and FreeBusyDetailed does not apply here [MS-OXCFOLD] Section 2.2.2.2.2.8 */ @@ -1664,6 +1671,7 @@ NSString *MAPIStoreRightFolderContact = @"RightsFolderContact"; NSArray *roles; roles = [[self aclFolder] aclsForUser: [activeUser login]]; + roles = [self expandRoles: roles]; // Not required if (([self exchangeRightsForRoles: roles] & RightsFolderOwner) == 0) return MAPISTORE_ERR_DENIED; } diff --git a/OpenChange/MAPIStoreGCSFolder.m b/OpenChange/MAPIStoreGCSFolder.m index c70e1f1c3..9b269df35 100644 --- a/OpenChange/MAPIStoreGCSFolder.m +++ b/OpenChange/MAPIStoreGCSFolder.m @@ -859,6 +859,7 @@ static Class NSNumberK; woContext = [[self userContext] woContext]; activeUserRoles = [activeUser rolesForObject: sogoObject inContext: woContext]; + activeUserRoles = [self expandRoles: activeUserRoles]; [activeUserRoles retain]; } diff --git a/OpenChange/MAPIStoreMessage.h b/OpenChange/MAPIStoreMessage.h index 2f059733e..b7441a74c 100644 --- a/OpenChange/MAPIStoreMessage.h +++ b/OpenChange/MAPIStoreMessage.h @@ -69,6 +69,7 @@ - (NSArray *) activeContainerMessageTables; - (NSArray *) activeUserRoles; +- (NSArray *) expandRoles: (NSArray *) roles; /* move & copy internal ops */ - (void) copyToMessage: (MAPIStoreMessage *) newMessage inMemCtx: (TALLOC_CTX *) memCtx; diff --git a/OpenChange/MAPIStoreMessage.m b/OpenChange/MAPIStoreMessage.m index 5f341f600..3e303e5a3 100644 --- a/OpenChange/MAPIStoreMessage.m +++ b/OpenChange/MAPIStoreMessage.m @@ -981,12 +981,22 @@ rtf2html (NSData *compressedRTF) activeUserRoles = [[context activeUser] rolesForObject: sogoObject inContext: [userContext woContext]]; + /* We use in this library the roles as flags, so we expand high + access rights with the lower ones */ + activeUserRoles = [self expandRoles: activeUserRoles]; [activeUserRoles retain]; } return activeUserRoles; } +/* Expand current roles with lower access roles to transform them to + flags */ +- (NSArray *) expandRoles: (NSArray *) roles +{ + return roles; +} + /* Can the current active user read the message? */ - (BOOL) subscriberCanReadMessage { diff --git a/OpenChange/MAPIStorePermissionsTable.m b/OpenChange/MAPIStorePermissionsTable.m index 193ef9a77..3bd2f84b7 100644 --- a/OpenChange/MAPIStorePermissionsTable.m +++ b/OpenChange/MAPIStorePermissionsTable.m @@ -134,6 +134,7 @@ NSArray *roles; roles = [[(MAPIStoreFolder *) container aclFolder] aclsForUser: userId]; + roles = [(MAPIStoreFolder *) container expandRoles: roles]; rights = [(MAPIStoreFolder *) container exchangeRightsForRoles: roles]; *data = MAPILongValue (memCtx, rights); diff --git a/OpenChange/MAPIStoreTasksFolder.h b/OpenChange/MAPIStoreTasksFolder.h index dc9b6791a..3d16a44ae 100644 --- a/OpenChange/MAPIStoreTasksFolder.h +++ b/OpenChange/MAPIStoreTasksFolder.h @@ -23,9 +23,9 @@ #ifndef MAPISTORETASKSFOLDER_H #define MAPISTORETASKSFOLDER_H -#import "MAPIStoreGCSFolder.h" +#import "MAPIStoreCalTaskFolder.h" -@interface MAPIStoreTasksFolder : MAPIStoreGCSFolder +@interface MAPIStoreTasksFolder : MAPIStoreCalTaskFolder @end diff --git a/OpenChange/MAPIStoreTasksMessage.h b/OpenChange/MAPIStoreTasksMessage.h index 3130b72a5..360c516ad 100644 --- a/OpenChange/MAPIStoreTasksMessage.h +++ b/OpenChange/MAPIStoreTasksMessage.h @@ -23,9 +23,9 @@ #ifndef MAPISTORETASKSMESSAGE_H #define MAPISTORETASKSMESSAGE_H -#import "MAPIStoreGCSMessage.h" +#import "MAPIStoreCalTaskMessage.h" -@interface MAPIStoreTasksMessage : MAPIStoreGCSMessage +@interface MAPIStoreTasksMessage : MAPIStoreCalTaskMessage @end diff --git a/OpenChange/MAPIStoreTasksMessage.m b/OpenChange/MAPIStoreTasksMessage.m index 382c61fa0..3cd3f1bfd 100644 --- a/OpenChange/MAPIStoreTasksMessage.m +++ b/OpenChange/MAPIStoreTasksMessage.m @@ -129,12 +129,23 @@ task = [sogoObject component: NO secure: YES]; - if ([task symbolicAccessClass] == iCalAccessPublic) + if ([task isPublic]) return [self getNo: data inMemCtx: memCtx]; return [self getYes: data inMemCtx: memCtx]; } +- (enum mapistore_error) getPidTagSensitivity: (void **) data + inMemCtx: (TALLOC_CTX *) memCtx +{ + uint32_t v; + + v = (uint32_t) [self sensitivity]; + + *data = MAPILongValue (memCtx, v); + return MAPISTORE_SUCCESS; +} + - (enum mapistore_error) getPidTagImportance: (void **) data inMemCtx: (TALLOC_CTX *) memCtx { @@ -343,6 +354,28 @@ // ---------------------------------- // Sharing // ---------------------------------- +- (NSUInteger) sensitivity +{ + iCalToDo *task; + NSUInteger v; + + task = [sogoObject component: NO secure: YES]; + /* FIXME: Use OpenChange constants names */ + switch ([task symbolicAccessClass]) + { + case iCalAccessPrivate: + v = 0x2; + break; + case iCalAccessConfidential: + v = 0x3; + break; + default: + v = 0x0; + break; + } + return v; +} + - (NSString *) creator { iCalToDo *task; @@ -359,36 +392,6 @@ return [self creator]; } -- (BOOL) subscriberCanReadMessage -{ - return ([[self activeUserRoles] - containsObject: SOGoCalendarRole_ComponentViewer] - || [self subscriberCanModifyMessage]); -} - -- (BOOL) subscriberCanModifyMessage -{ - BOOL rc; - NSArray *roles = [self activeUserRoles]; - - if (isNew) - rc = [roles containsObject: SOGoRole_ObjectCreator]; - else - rc = ([roles containsObject: SOGoCalendarRole_ComponentModifier] - || [roles containsObject: SOGoCalendarRole_ComponentResponder]); - - /* Check if the message is owned and it has permission to edit it */ - if (!rc && [roles containsObject: MAPIStoreRightEditOwn]) - { - NSString *currentUser; - - currentUser = [[container context] activeUser]; - rc = [currentUser isEqual: [self ownerUser]]; - } - - return rc; -} - - (void) save:(TALLOC_CTX *) memCtx { iCalCalendar *vCalendar; From 698918abf927fe7aa63ee3f70dfb7e02e5afd2db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jes=C3=BAs=20Garc=C3=ADa=20S=C3=A1ez?= Date: Tue, 23 Feb 2016 15:08:15 +0100 Subject: [PATCH 19/19] Fix prepareRightsForm, keep the highest role userRights is sorted highest to lowest right so we have to assign only the first right found for each type --- UI/Scheduler/UIxCalUserRightsEditor.m | 31 +++++++++++++-------------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/UI/Scheduler/UIxCalUserRightsEditor.m b/UI/Scheduler/UIxCalUserRightsEditor.m index 00534bff8..91738707a 100644 --- a/UI/Scheduler/UIxCalUserRightsEditor.m +++ b/UI/Scheduler/UIxCalUserRightsEditor.m @@ -57,25 +57,24 @@ { NSEnumerator *roles, *types; NSString *role, *type; - unsigned int length; - roles = [userRights objectEnumerator]; - role = [roles nextObject]; - while (role) + types = [[self rightTypes] objectEnumerator]; + type = [types nextObject]; + while (type) { - types = [[self rightTypes] objectEnumerator]; - type = [types nextObject]; - while (type) - { - if ([role hasPrefix: type]) - { - length = [type length]; - [rights setObject: [role substringFromIndex: length] - forKey: type]; - } - type = [types nextObject]; - } + roles = [userRights objectEnumerator]; role = [roles nextObject]; + while (role) + { + if ([role hasPrefix: type]) + { + [rights setObject: [role substringFromIndex: [type length]] + forKey: type]; + break; + } + role = [roles nextObject]; + } + type = [types nextObject]; } }