2 Copyright (C) 2004-2005 SKYRIX Software AG
4 This file is part of OpenGroupware.org.
6 OGo is free software; you can redistribute it and/or modify it under
7 the terms of the GNU Lesser General Public License as published by the
8 Free Software Foundation; either version 2, or (at your option) any
11 OGo is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
14 License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with OGo; see the file COPYING. If not, write to the
18 Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
22 #if LIB_FOUNDATION_LIBRARY
23 #error SOGo will not work properly with libFoundation. \
24 Please use gnustep-base instead.
27 #import <NGObjWeb/WEClientCapabilities.h>
28 #import <NGObjWeb/SoObject+SoDAV.h>
29 #import <NGObjWeb/WOContext.h>
30 #import <NGObjWeb/WOResponse.h>
31 #import <NGObjWeb/WORequest.h>
32 #import <NGObjWeb/WOApplication.h>
33 #import <NGCards/NSDictionary+NGCards.h>
34 #import <GDLContentStore/GCSFolder.h>
38 #import "NSArray+Utilities.h"
39 #import "NSString+Utilities.h"
41 #import "SOGoPermissions.h"
43 #import "SOGoAuthenticator.h"
44 #import "SOGoUserFolder.h"
46 #import "SOGoDAVRendererTypes.h"
48 #import "SOGoObject.h"
50 NSString *SOGoDefaultUserID = @"<default>";
52 @interface SOGoObject(Content)
53 - (NSString *)contentAsString;
56 @interface SoClassSecurityInfo (SOGoAcls)
58 + (id) defaultWebDAVPermissionsMap;
60 - (NSArray *) allPermissions;
61 - (NSArray *) allDAVPermissions;
62 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
63 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
67 @implementation SoClassSecurityInfo (SOGoAcls)
69 + (id) defaultWebDAVPermissionsMap
71 return [NSDictionary dictionaryWithObjectsAndKeys:
72 @"read", SoPerm_AccessContentsInformation,
74 @"bind", SoPerm_AddDocumentsImagesAndFiles,
75 @"unbind", SoPerm_DeleteObjects,
76 @"write-acl", SoPerm_ChangePermissions,
77 @"write-content", SoPerm_ChangeImagesAndFiles,
78 @"read-free-busy", SOGoPerm_FreeBusyLookup,
82 - (NSArray *) allPermissions
84 return [defRoles allKeys];
87 - (NSArray *) allDAVPermissions
89 NSEnumerator *allPermissions;
90 NSMutableArray *davPermissions;
91 NSDictionary *davPermissionsMap;
92 NSString *sopePermission, *davPermission;
94 davPermissions = [NSMutableArray array];
96 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
97 allPermissions = [[self allPermissions] objectEnumerator];
98 sopePermission = [allPermissions nextObject];
99 while (sopePermission)
101 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
102 if (davPermission && ![davPermissions containsObject: davPermission])
103 [davPermissions addObject: davPermission];
104 sopePermission = [allPermissions nextObject];
107 return davPermissions;
110 - (NSArray *) DAVPermissionsForRole: (NSString *) role
112 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
115 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
117 NSEnumerator *allPermissions;
118 NSMutableArray *davPermissions;
119 NSDictionary *davPermissionsMap;
120 NSString *sopePermission, *davPermission;
122 davPermissions = [NSMutableArray array];
124 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
125 allPermissions = [[self allPermissions] objectEnumerator];
126 sopePermission = [allPermissions nextObject];
127 while (sopePermission)
129 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
130 firstObjectCommonWithArray: roles])
133 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
135 && ![davPermissions containsObject: davPermission])
136 [davPermissions addObject: davPermission];
138 sopePermission = [allPermissions nextObject];
141 return davPermissions;
146 @implementation SOGoObject
148 static BOOL kontactGroupDAV = YES;
156 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
159 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
161 // SoClass security declarations
163 // require View permission to access the root (bound to authenticated ...)
164 [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
166 // to allow public access to all contained objects (subkeys)
167 [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
169 // /* require Authenticated role for View and WebDAV */
170 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
171 // asDefaultForPermission: SoPerm_View];
172 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
173 // asDefaultForPermission: SoPerm_WebDAVAccess];
176 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
177 withDAVMethods: (NSString *) firstMethod, ...
180 NSString *aclMethodName;
181 NSString *methodName;
184 va_start (ap, firstMethod);
185 aclMethodName = firstMethod;
186 while (aclMethodName)
188 methodName = [aclMethodName davMethodToObjC];
189 methodSel = NSSelectorFromString (methodName);
190 if (methodSel && [self instancesRespondToSelector: methodSel])
191 [dictionary setObject: methodName
192 forKey: [NSString stringWithFormat: @"{DAV:}%@",
195 NSLog(@"************ method '%@' is still unimplemented!",
197 aclMethodName = va_arg (ap, NSString *);
203 + (NSDictionary *) defaultWebDAVAttributeMap
205 static NSMutableDictionary *map = nil;
209 map = [NSMutableDictionary
210 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
212 [self _fillDictionary: map
213 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
214 @"current-user-privilege-set", @"acl", @"acl-restrictions",
215 @"inherited-acl-set", @"principal-collection-set", nil];
223 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
227 object = [[self alloc] initWithName: _name inContainer: _container];
228 [object autorelease];
233 /* DAV ACL properties */
234 - (NSString *) davOwner
236 return [NSString stringWithFormat: @"%@users/%@",
237 [self rootURLInContext: context],
238 [self ownerInContext: nil]];
241 - (NSString *) davAclRestrictions
243 NSMutableString *restrictions;
245 restrictions = [NSMutableString string];
246 [restrictions appendString: @"<D:grant-only/>"];
247 [restrictions appendString: @"<D:no-invert/>"];
252 - (SOGoDAVSet *) davPrincipalCollectionSet
256 usersUrl = [NSString stringWithFormat: @"%@users",
257 [self rootURLInContext: context]];
259 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
260 ofValuesTaggedAs: @"D:href"];
263 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
265 SOGoAuthenticator *sAuth;
268 SoClassSecurityInfo *sInfo;
269 NSArray *davPermissions;
271 sAuth = [SOGoAuthenticator sharedSOGoAuthenticator];
272 user = [sAuth userInContext: context];
273 roles = [user rolesForObject: self inContext: context];
274 sInfo = [[self class] soClassSecurityInfo];
277 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
279 return [SOGoDAVSet davSetWithArray: davPermissions
280 ofValuesTaggedAs: @"D:privilege"];
283 - (SOGoDAVSet *) davSupportedPrivilegeSet
285 SoClassSecurityInfo *sInfo;
286 NSArray *allPermissions;
288 sInfo = [[self class] soClassSecurityInfo];
290 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
292 return [SOGoDAVSet davSetWithArray: allPermissions
293 ofValuesTaggedAs: @"D:privilege"];
296 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
300 NSMutableString *currentAce;
301 NSMutableArray *davAces;
302 NSString *currentKey, *principal;
303 SOGoDAVSet *privilegesDS;
305 davAces = [NSMutableArray array];
306 keys = [[aclsDictionary allKeys] objectEnumerator];
307 currentKey = [keys nextObject];
310 currentAce = [NSMutableString string];
311 if ([currentKey hasPrefix: @":"])
313 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
314 [currentKey substringFromIndex: 1]];
317 principal = [NSString stringWithFormat: @"%@users/%@",
318 [self rootURLInContext: context],
321 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
325 privileges = [[aclsDictionary objectForKey: currentKey]
326 stringsWithFormat: @"<D:%@/>"];
327 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
328 ofValuesTaggedAs: @"privilege"];
329 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
331 inContext: nil prefixes: nil]];
332 [davAces addObject: currentAce];
333 currentKey = [keys nextObject];
339 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
340 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
344 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
346 [aclsDictionary setObject: perms forKey: @":owner"];
347 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
349 [aclsDictionary setObject: perms forKey: @":authenticated"];
350 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
352 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
355 - (SOGoDAVSet *) davAcl
359 NSMutableDictionary *aclsDictionary;
360 NSDictionary *currentAcl;
361 SoClassSecurityInfo *sInfo;
363 acls = [[self acls] objectEnumerator];
364 aclsDictionary = [NSMutableDictionary dictionary];
365 sInfo = [[self class] soClassSecurityInfo];
367 currentAcl = [acls nextObject];
370 role = [NSArray arrayWithObject: [currentAcl objectForKey: @"role"]];
371 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: role]
372 forKey: [currentAcl objectForKey: @"uid"]];
373 currentAcl = [acls nextObject];
375 [self _appendRolesForPseudoPrincipals: aclsDictionary
376 withClassSecurityInfo: sInfo];
378 return [SOGoDAVSet davSetWithArray:
379 [self _davAcesFromAclsDictionary: aclsDictionary]
380 ofValuesTaggedAs: @"D:ace"];
383 /* end of properties */
385 - (BOOL) doesRetainContainer
390 - (id)initWithName:(NSString *)_name inContainer:(id)_container {
391 if ((self = [self init]))
393 context = [[WOApplication application] context];
395 nameInContainer = [_name copy];
397 [self doesRetainContainer] ? [_container retain] : _container;
406 if ((self = [super init]))
408 nameInContainer = nil;
417 [customOwner release];
418 if ([self doesRetainContainer])
420 [nameInContainer release];
426 - (NSString *)nameInContainer {
427 return nameInContainer;
435 - (void) setOwner: (NSString *) newOwner
437 ASSIGN (customOwner, newOwner);
440 - (NSString *)ownerInContext:(id)_ctx {
441 return ((customOwner)
443 : [[self container] ownerInContext:_ctx]);
448 - (NSArray *)fetchSubfolders {
453 if ((names = [self toManyRelationshipKeys]) == nil)
456 count = [names count];
457 ma = [NSMutableArray arrayWithCapacity:count + 1];
458 for (i = 0; i < count; i++) {
461 folder = [self lookupName: [names objectAtIndex:i]
466 if ([folder isKindOfClass:[NSException class]])
469 [ma addObject:folder];
474 /* looking up shared objects */
476 - (SOGoUserFolder *)lookupUserFolder {
477 if (![container respondsToSelector:_cmd])
480 return [container lookupUserFolder];
482 - (SOGoGroupsFolder *)lookupGroupsFolder {
483 return [[self lookupUserFolder] lookupGroupsFolder];
487 if ([self doesRetainContainer])
494 - (NSException *)delete {
495 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
496 reason:@"delete not yet implemented, sorry ..."];
501 - (id)valueForUndefinedKey:(NSString *)_key {
507 - (NSString *)davDisplayName {
508 return [self nameInContainer];
513 - (id)DELETEAction:(id)_ctx {
516 if ((error = [self delete]) != nil)
519 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
520 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
523 - (id)GETAction:(id)_ctx {
524 // TODO: I guess this should really be done by SOPE (redirect to
530 r = [(WOContext *)_ctx response];
531 rq = [(WOContext *)_ctx request];
533 if ([rq isSoWebDAVRequest]) {
534 if ([self respondsToSelector:@selector(contentAsString)]) {
538 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
541 [r appendContentString:[self contentAsString]];
543 if ((etag = [self davEntityTag]) != nil)
544 [r setHeader:etag forKey:@"etag"];
549 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
550 reason:@"no WebDAV GET support?!"];
554 [r setStatus:302 /* moved */];
555 [r setHeader: [uri composeURLWithAction: @"view"
556 parameters: [rq formValues]
565 - (NSArray *)parseETagList:(NSString *)_c {
570 if ([_c length] == 0)
572 if ([_c isEqualToString:@"*"])
575 etags = [_c componentsSeparatedByString:@","];
576 count = [etags count];
577 ma = [NSMutableArray arrayWithCapacity:count];
578 for (i = 0; i < count; i++) {
581 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
582 #if 0 /* this is non-sense, right? */
583 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
584 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
587 if (etag != nil) [ma addObject:etag];
592 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
594 Only run the request if one of the etags matches the resource etag,
595 usually used to ensure consistent PUTs.
600 if ([_c isEqualToString:@"*"])
601 /* to ensure that the resource exists! */
604 if ((etags = [self parseETagList:_c]) == nil)
606 if ([etags count] == 0) /* no etags to check for? */
609 etag = [self davEntityTag];
610 if ([etag length] == 0) /* has no etag, ignore */
613 if ([etags containsObject:etag]) {
614 [self debugWithFormat:@"etag '%@' matches: %@", etag,
615 [etags componentsJoinedByString:@","]];
616 return nil; /* one etag matches, so continue with request */
619 /* hack for Kontact 3.4 */
621 if (kontactGroupDAV) {
622 WEClientCapabilities *cc;
624 cc = [[(WOContext *)_ctx request] clientCapabilities];
625 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
626 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
628 @"WARNING: applying Kontact 3.4 GroupDAV hack"
629 @" - etag check is disabled!"
630 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
636 // TODO: we might want to return the davEntityTag in the response
637 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
638 [etags componentsJoinedByString:@","]];
639 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
640 reason:@"Precondition Failed"];
643 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
645 If one of the etags is still the same, we can ignore the request.
647 Can be used for PUT to ensure that the object does not exist in the store
648 and for GET to retrieve the content only if if the etag changed.
651 if (![_c isEqualToString:@"*"] &&
652 [[[_ctx request] method] isEqualToString:@"GET"]) {
656 if ((etags = [self parseETagList:_c]) == nil)
658 if ([etags count] == 0) /* no etags to check for? */
661 etag = [self davEntityTag];
662 if ([etag length] == 0) /* has no etag, ignore */
665 if ([etags containsObject:etag]) {
666 [self debugWithFormat:@"etag '%@' matches: %@", etag,
667 [etags componentsJoinedByString:@","]];
668 /* one etag matches, so stop the request */
669 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
670 reason:@"object was not modified"];
677 if ([_c isEqualToString:@"*"])
680 if ((a = [self parseETagList:_c]) == nil)
683 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
688 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
693 if ((rq = [(WOContext *)_ctx request]) == nil)
694 return nil; /* be tolerant - no request, no condition */
696 if ((c = [rq headerForKey:@"if-match"]) != nil) {
697 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
700 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
701 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
712 [self subclassResponsibility: _cmd];
717 - (NSArray *) aclsForUser: (NSString *) uid
719 [self subclassResponsibility: _cmd];
724 - (NSArray *) defaultAclRoles
726 [self subclassResponsibility: _cmd];
731 - (void) setRoles: (NSArray *) roles
732 forUser: (NSString *) uid
734 [self subclassResponsibility: _cmd];
737 - (void) removeAclsForUsers: (NSArray *) users
739 [self subclassResponsibility: _cmd];
744 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
746 [_ms appendFormat:@" name=%@", nameInContainer];
748 [_ms appendFormat:@" container=0x%08X/%@",
749 container, [container valueForKey:@"nameInContainer"]];
752 - (NSString *)description {
755 ms = [NSMutableString stringWithCapacity:64];
756 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
757 [self appendAttributesToDescription:ms];
758 [ms appendString:@">"];
763 @end /* SOGoObject */