diff --git a/ChangeLog b/ChangeLog index 54be19d5f..dd9b836fe 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,5 +1,47 @@ 2009-08-27 Wolfgang Sourdeau + * SoObjects/Appointments/SOGoCalendarProxy.[hm]: new class module + implementing the caldav proxy collections mentionned below. This + is a subclass of SOGoFolder. + + * SoObjects/SOGo/WORequest+SOGo.m (-isAppleDAVWithSubstring:): new + method. + (-isICal): new method. + + * SoObjects/SOGo/SOGoUserFolder.m + (-calendarProxy:withWriteAccess:): new method that instantiate a + proxy collections for read and write delegations. + + * SoObjects/SOGo/SOGoParentFolder.m (-initSubscribedFolders): we + now make use of appendSubcribedSources with which we shared code. + + * SoObjects/SOGo/SOGoGCSFolder.m (-subscribeUser:reallyDo:): new + simplified and published version of + subscribe:inTheNamesOf:fromMailInvitation:inContext:. + + * SoObjects/SOGo/SOGoFolder.m (-davGroupMemberSet) + (davGroupMembership): new DAV accessors that return an empty + array. + + * SoObjects/Appointments/SOGoUserFolder+Appointments.m + (-davGroupMembership): new DAV accessor. + + * SoObjects/Appointments/SOGoAppointmentFolders.m + (-toManyRelationshipKeys): overriden method for iCal: we don't + list the folders to which the current user is subscribed to + (because the iCal paradigm now requires calendar proxying), we + don't list the folders to which a delegator has subscribed to + either nor any of his/her secondary calendars. + (-proxyFoldersWithWriteAccess:): new methods that returns the list + of personal folder on which we are allowed to act as a proxy. + + * SoObjects/Appointments/SOGoAppointmentFolder.m + (-requiredProxyRolesWithWriteAccess:): new method that returnes + the roles required for read/write proxying of users. + (-proxySubscribersWithWriteAccess:) + (setProxySubscribers:withWriteAccess:): new accessors making use + of the new method above. + * Tests/test-caldav-scheduling.py: new set of tests for CalDAV scheduling (iTIP-over-DAV) operations. Implemented 9 scenarios for invitation delegation. diff --git a/NEWS b/NEWS index b292acdc0..0f4300ee9 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,7 @@ - a context menu is now available for tasks - added the capability of creating and managing lists of contacts (same as in Thunderbird) - added support for short date format in the calendar views +- added support for iCal delegation 1.0-20090812 (1.0.4) -------------------- diff --git a/SoObjects/Appointments/GNUmakefile b/SoObjects/Appointments/GNUmakefile index d74b980fa..38e2417a8 100644 --- a/SoObjects/Appointments/GNUmakefile +++ b/SoObjects/Appointments/GNUmakefile @@ -28,6 +28,8 @@ Appointments_OBJC_FILES = \ SOGoFreeBusyObject.m \ SOGoUserFolder+Appointments.m \ \ + SOGoCalendarProxy.m \ + \ SOGoAptMailNotification.m \ SOGoAptMailInvitation.m \ SOGoAptMailDeletion.m \ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.h b/SoObjects/Appointments/SOGoAppointmentFolder.h index a5e78b6fe..383a615c4 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.h +++ b/SoObjects/Appointments/SOGoAppointmentFolder.h @@ -140,6 +140,10 @@ - (BOOL) showCalendarTasks; - (void) setShowCalendarTasks: (BOOL) new; +- (NSArray *) proxySubscribersWithWriteAccess: (BOOL) hasWriteAccess; +- (NSException *) setProxySubscribers: (NSArray *) newSubscribers + withWriteAccess: (BOOL) hasWriteAccess; + @end #endif /* __Appointments_SOGoAppointmentFolder_H__ */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolder.m b/SoObjects/Appointments/SOGoAppointmentFolder.m index 20d1174d4..c6b439a13 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolder.m +++ b/SoObjects/Appointments/SOGoAppointmentFolder.m @@ -3055,4 +3055,122 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir return (![inactiveFolders containsObject: nameInContainer]); } +- (NSArray *) requiredProxyRolesWithWriteAccess: (BOOL) hasWriteAccess +{ + static NSArray *writeAccessRoles = nil; + static NSArray *readAccessRoles = nil; + + if (!writeAccessRoles) + { + writeAccessRoles = [NSArray arrayWithObjects: + SOGoCalendarRole_ConfidentialModifier, + SOGoRole_ObjectCreator, + SOGoRole_ObjectEraser, + SOGoCalendarRole_PrivateModifier, + SOGoCalendarRole_PublicModifier, + nil]; + [writeAccessRoles retain]; + } + + if (!readAccessRoles) + { + readAccessRoles = [NSArray arrayWithObjects: + SOGoCalendarRole_ConfidentialViewer, + SOGoCalendarRole_PrivateViewer, + SOGoCalendarRole_PublicViewer, + nil]; + [readAccessRoles retain]; + } + + return (hasWriteAccess) ? writeAccessRoles : readAccessRoles; +} + +- (BOOL) _user: (NSString *) user + isProxyWithWriteAccess: (BOOL) hasWriteAccess +{ + NSArray *userRoles, *reqRoles; + BOOL isProxy; + + if ([self userIsSubscriber: user]) + { + userRoles = [[self aclsForUser: user] + sortedArrayUsingSelector: @selector (compare:)]; + reqRoles = [self requiredProxyRolesWithWriteAccess: hasWriteAccess]; + isProxy = [reqRoles isEqualToArray: userRoles]; + } + else + isProxy = NO; + + return isProxy; +} + +- (NSArray *) proxySubscribersWithWriteAccess: (BOOL) hasWriteAccess +{ + NSMutableArray *subscribers; + NSEnumerator *aclUsers; + NSString *currentUser, *defaultUser; + + subscribers = [NSMutableArray array]; + + defaultUser = [self defaultUserID]; + aclUsers = [[self aclUsers] objectEnumerator]; + while ((currentUser = [aclUsers nextObject])) + { + if (![currentUser isEqualToString: defaultUser] + && [self _user: currentUser + isProxyWithWriteAccess: hasWriteAccess]) + [subscribers addObject: currentUser]; + } + + return subscribers; +} + +- (NSException *) setProxySubscribers: (NSArray *) newSubscribers + withWriteAccess: (BOOL) hasWriteAccess +{ + int count, max; + NSArray *oldSubscribers; + NSString *login, *reason; + NSException *error; + + error = nil; + + max = [newSubscribers count]; + for (count = 0; !error && count < max; count++) + { + login = [newSubscribers objectAtIndex: count]; + if (![SOGoUser userWithLogin: login roles: nil]) + { + reason = [NSString stringWithFormat: @"User '%@' does not exist.", login]; + error = [NSException exceptionWithHTTPStatus: 403 reason: reason]; + } + } + + if (!error) + { + oldSubscribers = [self proxySubscribersWithWriteAccess: hasWriteAccess]; + for (count = 0; !error && count < max; count++) + { + login = [newSubscribers objectAtIndex: count]; + [self + setRoles: [self requiredProxyRolesWithWriteAccess: hasWriteAccess] + forUser: login]; + [self subscribeUser: login reallyDo: YES]; + } + + max = [oldSubscribers count]; + for (count = 0; count < max; count++) + { + login = [oldSubscribers objectAtIndex: count]; + if (![newSubscribers containsObject: login]) + { + [self subscribeUser: login reallyDo: NO]; + [self removeAclsForUsers: [NSArray arrayWithObject: login]]; + } + } + } + + return error; +} + @end /* SOGoAppointmentFolder */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.h b/SoObjects/Appointments/SOGoAppointmentFolders.h index 617674b60..d4f5d7698 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.h +++ b/SoObjects/Appointments/SOGoAppointmentFolders.h @@ -25,8 +25,12 @@ #import +@class NSArray; + @interface SOGoAppointmentFolders : SOGoParentFolder +- (NSArray *) proxyFoldersWithWriteAccess: (BOOL) hasWriteAccess; + @end #endif /* SOGOAPPOINTMENTFOLDERS_H */ diff --git a/SoObjects/Appointments/SOGoAppointmentFolders.m b/SoObjects/Appointments/SOGoAppointmentFolders.m index 4917c6abd..6c2c6f1db 100644 --- a/SoObjects/Appointments/SOGoAppointmentFolders.m +++ b/SoObjects/Appointments/SOGoAppointmentFolders.m @@ -26,17 +26,26 @@ #import #import -#import +#import #import #import + #import + #import #import #import +#import #import "SOGoAppointmentFolder.h" #import "SOGoAppointmentFolders.h" +@interface SOGoParentFolder (Private) + +- (NSException *) initSubscribedSubFolders; + +@end + @implementation SOGoAppointmentFolders + (NSString *) gcsFolderType @@ -54,6 +63,36 @@ return [self labelForKey: @"Personal Calendar"]; } +- (NSArray *) toManyRelationshipKeys +{ + NSEnumerator *sortedSubFolders; + SOGoGCSFolder *currentFolder; + NSString *login; + NSMutableArray *keys; + + login = [[context activeUser] login]; + if ([[context request] isICal]) + { + keys = [NSMutableArray array]; + if ([owner isEqualToString: login]) + { + sortedSubFolders = [[self subFolders] objectEnumerator]; + while ((currentFolder = [sortedSubFolders nextObject])) + { + if ([[currentFolder ownerInContext: context] + isEqualToString: owner]) + [keys addObject: [currentFolder nameInContainer]]; + } + } + else + [keys addObject: @"personal"]; + } + else + keys = (NSMutableArray *) [super toManyRelationshipKeys]; + + return keys; +} + - (NSString *) _fetchPropertyWithName: (NSString *) propertyName inArray: (NSArray *) section { @@ -199,4 +238,35 @@ return componentSet; } +- (NSArray *) proxyFoldersWithWriteAccess: (BOOL) hasWriteAccess +{ + NSMutableArray *proxyFolders; + NSArray *proxySubscribers; + NSEnumerator *folders; + SOGoAppointmentFolder *currentFolder; + NSString *folderOwner, *currentUser; + + proxyFolders = [NSMutableArray new]; + + currentUser = [[context activeUser] login]; + + [self initSubscribedSubFolders]; + folders = [subscribedSubFolders objectEnumerator]; + while ((currentFolder = [folders nextObject])) + { + folderOwner = [currentFolder ownerInContext: context]; + /* we currently only list the users of which we have subscribed to the + personal folder */ + if ([[currentFolder realNameInContainer] isEqualToString: @"personal"]) + { + proxySubscribers + = [currentFolder proxySubscribersWithWriteAccess: hasWriteAccess]; + if ([proxySubscribers containsObject: currentUser]) + [proxyFolders addObject: currentFolder]; + } + } + + return proxyFolders; +} + @end diff --git a/SoObjects/Appointments/SOGoCalendarProxy.h b/SoObjects/Appointments/SOGoCalendarProxy.h new file mode 100644 index 000000000..4279f67f6 --- /dev/null +++ b/SoObjects/Appointments/SOGoCalendarProxy.h @@ -0,0 +1,37 @@ +/* SOGoCalendarProxy.h - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * 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 SOGOCALENDARPROXY_H +#define SOGOCALENDARPROXY_H + +#import + +@interface SOGoCalendarProxy : SOGoFolder +{ + BOOL hasWriteAccess; +} + +- (void) setWriteAccess: (BOOL) newHasWriteAccess; + +@end + +#endif /* SOGOCALENDARPROXY_H */ diff --git a/SoObjects/Appointments/SOGoCalendarProxy.m b/SoObjects/Appointments/SOGoCalendarProxy.m new file mode 100644 index 000000000..711d487e7 --- /dev/null +++ b/SoObjects/Appointments/SOGoCalendarProxy.m @@ -0,0 +1,156 @@ +/* SOGoCalendarProxy.m - this file is part of SOGo + * + * Copyright (C) 2009 Inverse inc. + * + * Author: Wolfgang Sourdeau + * + * 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 + +#import "SOGoAppointmentFolder.h" +#import "SOGoCalendarProxy.h" + +@implementation SOGoCalendarProxy + +#define XMLNS_CALENDARSERVER @"http://calendarserver.org/ns/" + +- (id) init +{ + if ((self = [super init])) + { + hasWriteAccess = NO; + } + + return self; +} + +- (void) setWriteAccess: (BOOL) newHasWriteAccess +{ + hasWriteAccess = newHasWriteAccess; +} + +- (NSArray *) davResourceType +{ + NSString *proxyType; + NSMutableArray *rType; + + rType = [NSMutableArray arrayWithArray: [super davResourceType]]; + [rType addObject: @"principal"]; + if (hasWriteAccess) + proxyType = @"calendar-proxy-write"; + else + proxyType = @"calendar-proxy-read"; + [rType addObject: [NSArray arrayWithObjects: proxyType, + XMLNS_CALENDARSERVER, nil]]; + + return rType; +} + +- (NSArray *) davGroupMemberSet +{ + NSMutableArray *members; + NSEnumerator *subscribers; + NSArray *member; + SOGoUser *ownerUser; + SOGoAppointmentFolder *folder; + NSString *subscriber; + + members = [NSMutableArray array]; + + ownerUser = [SOGoUser userWithLogin: [self ownerInContext: context] + roles: nil]; + folder = [ownerUser personalCalendarFolderInContext: context]; + subscribers = [[folder proxySubscribersWithWriteAccess: hasWriteAccess] + objectEnumerator]; + while ((subscriber = [subscribers nextObject])) + { + member = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + [NSString stringWithFormat: @"/SOGo/dav/%@/", + subscriber], + nil]; + [members addObject: member]; + } + + return members; +} + +- (NSString *) davGroupMembership +{ + return nil; +} + +- (NSString *) _parseSubscriber: (NSString *) memberSet + until: (int) length +{ + int begin, end; + NSRange beginRange; + + end = length; + if ([memberSet characterAtIndex: end - 1] == '/') + end--; + beginRange = [memberSet rangeOfString: @"/" + options: NSBackwardsSearch + range: NSMakeRange (0, end)]; + begin = NSMaxRange (beginRange); + + return [memberSet substringWithRange: NSMakeRange (begin, end - begin)]; +} + +- (NSArray *) _parseSubscribers: (NSString *) memberSet +{ + NSRange endRange; + NSMutableArray *subscribers; + NSMutableString *mMemberSet; + NSString *subscriber; + + subscribers = [NSMutableArray array]; + mMemberSet = [NSMutableString stringWithString: memberSet]; + + endRange = [mMemberSet rangeOfString: @" #import +#import #import #import #import +#import "SOGoAppointmentFolders.h" #import "SOGoUserFolder+Appointments.h" @interface SOGoUserFolder (private) @@ -431,4 +433,47 @@ // } +- (void) _addFolders: (NSEnumerator *) folders + withGroupTag: (NSString *) groupTag + toArray: (NSMutableArray *) groups +{ + SOGoAppointmentFolder *currentFolder; + NSString *folderOwner; + NSArray *tag; + + while ((currentFolder = [folders nextObject])) + { + folderOwner = [currentFolder ownerInContext: context]; + tag = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + [NSString stringWithFormat: @"/SOGo/dav/%@/%@/", + folderOwner, groupTag], + nil]; + [groups addObject: tag]; + } +} + +- (NSArray *) davGroupMembership +{ + SOGoAppointmentFolders *calendars; + NSArray *writeFolders, *readFolders; + NSMutableArray *groups; + + groups = [NSMutableArray array]; + + [self ownerInContext: context]; + + calendars = [self privateCalendars: @"Calendar" inContext: context]; + writeFolders = [calendars proxyFoldersWithWriteAccess: YES]; + [self _addFolders: [writeFolders objectEnumerator] + withGroupTag: @"calendar-proxy-write" + toArray: groups]; + + readFolders = [calendars proxyFoldersWithWriteAccess: NO]; + [self _addFolders: [readFolders objectEnumerator] + withGroupTag: @"calendar-proxy-read" + toArray: groups]; + + return groups; +} + @end diff --git a/SoObjects/SOGo/SOGoFolder.m b/SoObjects/SOGo/SOGoFolder.m index 823d08ca7..cd02a43a2 100644 --- a/SoObjects/SOGo/SOGoFolder.m +++ b/SoObjects/SOGo/SOGoFolder.m @@ -245,6 +245,11 @@ return rType; } +- (BOOL) davIsCollection +{ + return YES; +} + /* web dav acl helper */ - (void) _fillArrayWithPrincipalsOwnedBySelf: (NSMutableArray *) hrefs { @@ -263,6 +268,17 @@ acquire: NO] _fillArrayWithPrincipalsOwnedBySelf: hrefs]; } + +- (NSArray *) davGroupMemberSet +{ + return [NSArray array]; +} + +- (NSArray *) davGroupMembership +{ + return [NSArray array]; +} + /* folder type */ - (BOOL) isEqual: (id) otherFolder diff --git a/SoObjects/SOGo/SOGoGCSFolder.h b/SoObjects/SOGo/SOGoGCSFolder.h index 425447dcf..25e5d6e1a 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.h +++ b/SoObjects/SOGo/SOGoGCSFolder.h @@ -87,10 +87,9 @@ - (NSException *) delete; - (void) renameTo: (NSString *) newName; -- (WOResponse *) subscribe: (BOOL) reallyDo - inTheNamesOf: (NSArray *) delegatedUsers - fromMailInvitation: (BOOL) isMailInvitation - inContext: (WOContext *) localContext; +- (BOOL) subscribeUser: (NSString *) subscribingUser + reallyDo: (BOOL) reallyDo; +- (BOOL) userIsSubscriber: (NSString *) subscribingUser; - (void) initializeQuickTablesAclsInContext: (WOContext *) localContext; diff --git a/SoObjects/SOGo/SOGoGCSFolder.m b/SoObjects/SOGo/SOGoGCSFolder.m index b4eec7d26..b89ded31d 100644 --- a/SoObjects/SOGo/SOGoGCSFolder.m +++ b/SoObjects/SOGo/SOGoGCSFolder.m @@ -650,26 +650,34 @@ static NSArray *childRecordFields = nil; return cTag; } -#warning this code should be cleaned up -- (void) _subscribeUser: (SOGoUser *) subscribingUser - reallyDo: (BOOL) reallyDo - fromMailInvitation: (BOOL) isMailInvitation - inResponse: (WOResponse *) response +- (BOOL) userIsSubscriber: (NSString *) subscribingUser +{ + SOGoUser *sogoUser; + NSDictionary *moduleSettings; + NSArray *folderSubscription; + + sogoUser = [SOGoUser userWithLogin: subscribingUser roles: nil]; + moduleSettings = [[sogoUser userSettings] + objectForKey: [container nameInContainer]]; + folderSubscription = [moduleSettings objectForKey: @"SubscribedFolders"]; + + return [folderSubscription containsObject: [self folderReference]]; +} + +- (BOOL) subscribeUser: (NSString *) subscribingUser + reallyDo: (BOOL) reallyDo { NSMutableArray *folderSubscription, *tmpA; - NSString *subscriptionPointer, *mailInvitationURL; + NSString *subscriptionPointer; NSUserDefaults *ud; NSMutableDictionary *moduleSettings, *tmpD; + SOGoUser *sogoUser; + BOOL rc; - if ([owner isEqualToString: [subscribingUser login]]) + sogoUser = [SOGoUser userWithLogin: subscribingUser roles: nil]; + if (sogoUser) { - [response setStatus: 403]; - [response appendContentString: - @"You cannot (un)subscribe to a folder that you own!"]; - } - else - { - ud = [subscribingUser userSettings]; + ud = [sogoUser userSettings]; moduleSettings = [ud objectForKey: [container nameInContainer]]; if (!(moduleSettings && [moduleSettings isKindOfClass: [NSMutableDictionary class]])) @@ -687,7 +695,7 @@ static NSArray *childRecordFields = nil; [moduleSettings setObject: folderSubscription forKey: @"SubscribedFolders"]; } - + subscriptionPointer = [self folderReference]; if (reallyDo) [folderSubscription addObjectUniquely: subscriptionPointer]; @@ -717,82 +725,21 @@ static NSArray *childRecordFields = nil; } [ud synchronize]; - - if (isMailInvitation) - { - mailInvitationURL = [[self soURLToBaseContainerForCurrentUser] - absoluteString]; - [response setStatus: 302]; - [response setHeader: mailInvitationURL - forKey: @"location"]; - } - else - [response setStatus: 204]; - } -} - -- (WOResponse *) subscribe: (BOOL) reallyDo - inTheNamesOf: (NSArray *) delegatedUsers - fromMailInvitation: (BOOL) isMailInvitation - inContext: (WOContext *) localContext -{ - WOResponse *response; - SOGoUser *currentUser; - - response = [localContext response]; - [response setHeader: @"text/plain; charset=utf-8" - forKey: @"Content-Type"]; - - currentUser = [localContext activeUser]; - - if ([delegatedUsers count]) - { - if (![currentUser isSuperUser]) - { - [response setStatus: 403]; - [response appendContentString: - @"You cannot subscribe another user to any folder" - @" unless you are a super-user."]; - } - else - { - // The current user is a superuser... - SOGoUser *subscriptionUser; - int i; - - for (i = 0; i < [delegatedUsers count]; i++) - { - // We trust the passed user ID here as it might generate tons or LDAP - // call but more importantly, cache propagation calls that will create - // contention on GDNC. - subscriptionUser = [SOGoUser userWithLogin: [delegatedUsers objectAtIndex: i] - roles: nil - trust: YES]; - - [self _subscribeUser: subscriptionUser - reallyDo: reallyDo - fromMailInvitation: isMailInvitation - inResponse: response]; - } - } + rc = YES; } else - { - [self _subscribeUser: currentUser - reallyDo: reallyDo - fromMailInvitation: isMailInvitation - inResponse: response]; - } + rc = NO; - return response; + return rc; } -- (NSArray *) _parseDAVDelegatedUser: (WOContext *) queryContext +- (NSArray *) _parseDAVDelegatedUsers { id document; id attrs; id o; - document = [[queryContext request] contentAsDOMDocument]; + + document = [[context request] contentAsDOMDocument]; attrs = [[document documentElement] attributes]; o = [attrs namedItem: @"users"]; @@ -802,20 +749,65 @@ static NSArray *childRecordFields = nil; return nil; } +- (WOResponse *) _davSubscribe: (BOOL) reallyDo +{ + WOResponse *response; + SOGoUser *currentUser; + NSArray *delegatedUsers; + NSString *userLogin; + int count, max; + + response = [context response]; + [response setHeader: @"text/plain; charset=utf-8" + forKey: @"Content-Type"]; + [response setStatus: 204]; + + currentUser = [context activeUser]; + delegatedUsers = [self _parseDAVDelegatedUsers]; + + max = [delegatedUsers count]; + if (max) + { + if ([currentUser isSuperUser]) + { + /* We trust the passed user ID here as it might generate tons or + LDAP call but more importantly, cache propagation calls that will + create contention on GDNC. */ + for (count = 0; count < max; count++) + [self subscribeUser: [delegatedUsers objectAtIndex: count] + reallyDo: reallyDo]; + } + else + { + [response setStatus: 403]; + [response appendContentString: @"You cannot subscribe another user" + @" to any folder unless you are a super-user."]; + } + } + else + { + userLogin = [currentUser login]; + if ([owner isEqualToString: userLogin]) + { + [response setStatus: 403]; + [response appendContentString: + @"You cannot (un)subscribe to a folder that you own!"]; + } + else + [self subscribeUser: userLogin reallyDo: reallyDo]; + } + + return response; +} + - (id ) davSubscribe: (WOContext *) queryContext { - return [self subscribe: YES - inTheNamesOf: [self _parseDAVDelegatedUser: queryContext] - fromMailInvitation: NO - inContext: queryContext]; + return [self _davSubscribe: YES]; } - (id ) davUnsubscribe: (WOContext *) queryContext { - return [self subscribe: NO - inTheNamesOf: [self _parseDAVDelegatedUser: queryContext] - fromMailInvitation: NO - inContext: queryContext]; + return [self _davSubscribe: NO]; } - (NSDictionary *) davSQLFieldsTable diff --git a/SoObjects/SOGo/SOGoObject.m b/SoObjects/SOGo/SOGoObject.m index d8c316e9e..cc8a856db 100644 --- a/SoObjects/SOGo/SOGoObject.m +++ b/SoObjects/SOGo/SOGoObject.m @@ -1653,7 +1653,7 @@ SEL SOGoSelectorForPropertySetter (NSString *property) } else exception - = [NSException exceptionWithHTTPStatus: 404 + = [NSException exceptionWithHTTPStatus: 403 reason: [NSString stringWithFormat: @"Property '%@' cannot be set.", currentProp]]; diff --git a/SoObjects/SOGo/SOGoParentFolder.m b/SoObjects/SOGo/SOGoParentFolder.m index 4ec82951b..66f1bcbeb 100644 --- a/SoObjects/SOGo/SOGoParentFolder.m +++ b/SoObjects/SOGo/SOGoParentFolder.m @@ -107,6 +107,7 @@ static SoSecurityManager *sm = nil; { subFolders = nil; OCSPath = nil; + subscribedSubFolders = nil; subFolderClass = Nil; // hasSubscribedSources = NO; } @@ -116,6 +117,7 @@ static SoSecurityManager *sm = nil; - (void) dealloc { + [subscribedSubFolders release]; [subFolders release]; [OCSPath release]; [super dealloc]; @@ -353,29 +355,21 @@ static SoSecurityManager *sm = nil; - (NSException *) initSubscribedSubFolders { - NSArray *subscribedReferences; - NSUserDefaults *settings; - NSEnumerator *allKeys; - NSString *currentKey, *login; + NSString *login; NSException *error; + if (!subFolderClass) + subFolderClass = [[self class] subFolderClass]; + error = nil; /* we ignore non-DB errors at this time... */ login = [[context activeUser] login]; if (!subscribedSubFolders && [login isEqualToString: owner]) { subscribedSubFolders = [NSMutableDictionary new]; - settings = [[context activeUser] userSettings]; - subscribedReferences = [[settings objectForKey: nameInContainer] - objectForKey: @"SubscribedFolders"]; - if ([subscribedReferences isKindOfClass: [NSArray class]]) - { - allKeys = [subscribedReferences objectEnumerator]; - while ((currentKey = [allKeys nextObject])) - [self _appendSubscribedSource: currentKey]; - } + error = [self appendSubscribedSources]; } - + return error; } diff --git a/SoObjects/SOGo/SOGoUserFolder.m b/SoObjects/SOGo/SOGoUserFolder.m index bd82de9ad..cbc8c3d9e 100644 --- a/SoObjects/SOGo/SOGoUserFolder.m +++ b/SoObjects/SOGo/SOGoUserFolder.m @@ -43,6 +43,7 @@ #import #import +#import #import #import @@ -53,6 +54,7 @@ #import "LDAPUserManager.h" #import "SOGoPermissions.h" #import "SOGoUser.h" +#import "WORequest+SOGo.h" #import "SOGoUserFolder.h" @@ -84,11 +86,21 @@ static NSString *LDAPContactInfoAttribute = nil; currentUser = [context activeUser]; if ([currentUser canAccessModule: @"Calendar"]) - [children addObject: @"Calendar"]; + { + [children addObject: @"Calendar"]; + /* support for caldav-proxy, which is currently limited to iCal but may + be enabled for others later, once we sort out the consistency between + subscribe folders and "proxy collections". */ + if ([[context request] isICal]) + { + [children addObject: @"calendar-proxy-write"]; + [children addObject: @"calendar-proxy-read"]; + } + } [children addObject: @"Contacts"]; if ([currentUser canAccessModule: @"Mail"]) [children addObject: @"Mail"]; - [children addObject: @"Preferences"]; + // [children addObject: @"Preferences"]; return children; } @@ -529,6 +541,17 @@ static NSString *LDAPContactInfoAttribute = nil; return [$(@"SOGoFreeBusyObject") objectWithName: _key inContainer: self]; } +- (id) calendarProxy: (NSString *) name withWriteAccess: (BOOL) hasWrite +{ + id calendarProxy; + + calendarProxy = [$(@"SOGoCalendarProxy") objectWithName: name + inContainer: self]; + [calendarProxy setWriteAccess: hasWrite]; + + return calendarProxy; +} + - (id) lookupName: (NSString *) _key inContext: (WOContext *) _ctx acquire: (BOOL) _flag @@ -541,23 +564,29 @@ static NSString *LDAPContactInfoAttribute = nil; if (!obj) { currentUser = [_ctx activeUser]; - if ([_key isEqualToString: @"Calendar"]) - { - if ([currentUser canAccessModule: _key]) + if ([currentUser canAccessModule: @"Calendar"]) + { + if ([_key isEqualToString: @"Calendar"]) obj = [self privateCalendars: @"Calendar" inContext: _ctx]; + else if ([_key isEqualToString: @"freebusy.ifb"]) + obj = [self freeBusyObject:_key inContext: _ctx]; + else if ([_key isEqualToString: @"calendar-proxy-write"]) + obj = [self calendarProxy: _key withWriteAccess: YES]; + else if ([_key isEqualToString: @"calendar-proxy-read"]) + obj = [self calendarProxy: _key withWriteAccess: NO]; } - else if ([_key isEqualToString: @"Contacts"]) + + if (!obj + && [_key isEqualToString: @"Mail"] + && [currentUser canAccessModule: @"Mail"]) + obj = [self mailAccountsFolder: _key inContext: _ctx]; + + if (!obj && [_key isEqualToString: @"Contacts"]) obj = [self privateContacts: _key inContext: _ctx]; - else if ([_key isEqualToString: @"Mail"]) - { - if ([currentUser canAccessModule: _key]) - obj = [self mailAccountsFolder: _key inContext: _ctx]; - } - else if ([_key isEqualToString: @"Preferences"]) - obj = [$(@"SOGoPreferencesFolder") objectWithName: _key - inContainer: self]; - else if ([_key isEqualToString: @"freebusy.ifb"]) - obj = [self freeBusyObject:_key inContext: _ctx]; + + // else if ([_key isEqualToString: @"Preferences"]) + // obj = [$(@"SOGoPreferencesFolder") objectWithName: _key + // inContainer: self]; if (!obj) obj = [NSException exceptionWithHTTPStatus: 404 /* Not Found */]; @@ -584,9 +613,16 @@ static NSString *LDAPContactInfoAttribute = nil; getCNForUID: nameInContainer]; } -- (BOOL) davIsCollection +- (NSArray *) davPrincipalURL { - return YES; + NSArray *principalURL; + NSString *selfDAVPath; + + selfDAVPath = [[self davURL] path]; + principalURL = [NSArray arrayWithObjects: @"href", @"DAV:", @"D", + selfDAVPath, nil]; + + return [NSArray arrayWithObject: principalURL]; } @end /* SOGoUserFolder */ diff --git a/SoObjects/SOGo/WORequest+SOGo.h b/SoObjects/SOGo/WORequest+SOGo.h index b480f63a0..41e2c209a 100644 --- a/SoObjects/SOGo/WORequest+SOGo.h +++ b/SoObjects/SOGo/WORequest+SOGo.h @@ -29,7 +29,10 @@ - (BOOL) handledByDefaultHandler; - (NSDictionary *) davPatchedPropertiesWithTopTag: (NSString *) topTag; + +- (BOOL) isAppleDAVWithSubstring: (NSString *) osSubstring; - (BOOL) isIPhone; +- (BOOL) isICal; @end diff --git a/SoObjects/SOGo/WORequest+SOGo.m b/SoObjects/SOGo/WORequest+SOGo.m index e05b9fc9e..34855c895 100644 --- a/SoObjects/SOGo/WORequest+SOGo.m +++ b/SoObjects/SOGo/WORequest+SOGo.m @@ -103,22 +103,32 @@ return patchedProperties; } -- (BOOL) isIPhone +- (BOOL) isAppleDAVWithSubstring: (NSString *) osSubstring { WEClientCapabilities *cc; BOOL rc; + NSRange r; - rc = NO; cc = [self clientCapabilities]; if ([[cc userAgentType] isEqualToString: @"AppleDAVAccess"]) { - NSRange r = [[cc userAgent] rangeOfString: @"iPhone"]; - if (r.location != NSNotFound) - rc = YES; + r = [[cc userAgent] rangeOfString: osSubstring]; + rc = (r.location != NSNotFound); } + else + rc = NO; return rc; } +- (BOOL) isIPhone +{ + return [self isAppleDAVWithSubstring: @"iPhone/"]; +} + +- (BOOL) isICal +{ + return [self isAppleDAVWithSubstring: @"Mac OS X/10."]; +} @end diff --git a/UI/Common/UIxFolderActions.m b/UI/Common/UIxFolderActions.m index 00a7c7b02..3f5a17f5f 100644 --- a/UI/Common/UIxFolderActions.m +++ b/UI/Common/UIxFolderActions.m @@ -76,24 +76,48 @@ isMailInvitation = [mailInvitationParam boolValue]; } +- (WOResponse *) _subscribeAction: (BOOL) reallyDo +{ + WOResponse *response; + NSURL *mailInvitationURL; + + response = [context response]; + [response setHeader: @"text/plain; charset=utf-8" + forKey: @"Content-Type"]; + + [self _setupContext]; + if ([owner isEqualToString: login]) + { + [response setStatus: 403]; + [response appendContentString: + @"You cannot (un)subscribe to a folder that you own!"]; + } + else + { + [clientObject subscribeUser: login reallyDo: reallyDo]; + if (isMailInvitation) + { + mailInvitationURL + = [clientObject soURLToBaseContainerForCurrentUser]; + [response setStatus: 302]; + [response setHeader: [mailInvitationURL absoluteString] + forKey: @"location"]; + } + else + [response setStatus: 204]; + } + + return response; +} + - (WOResponse *) subscribeAction { - [self _setupContext]; - - return [clientObject subscribe: YES - inTheNamesOf: nil - fromMailInvitation: isMailInvitation - inContext: context]; + return [self _subscribeAction: YES]; } - (WOResponse *) unsubscribeAction { - [self _setupContext]; - - return [clientObject subscribe: NO - inTheNamesOf: nil - fromMailInvitation: isMailInvitation - inContext: context]; + return [self _subscribeAction: NO]; } - (WOResponse *) canAccessContentAction