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 #import <NGObjWeb/WEClientCapabilities.h>
23 #import <NGObjWeb/SoObject+SoDAV.h>
24 #import <NGObjWeb/WOContext.h>
25 #import <NGObjWeb/WOResponse.h>
26 #import <NGObjWeb/WORequest.h>
27 #import <NGObjWeb/WOApplication.h>
28 #import <NGCards/NSDictionary+NGCards.h>
29 #import <GDLContentStore/GCSFolder.h>
33 #import "NSArray+Utilities.h"
34 #import "NSString+Utilities.h"
36 #import "SOGoPermissions.h"
38 #import "SOGoAuthenticator.h"
39 #import "SOGoUserFolder.h"
41 #import "SOGoDAVRendererTypes.h"
42 #import "AgenorUserManager.h"
44 #import "SOGoObject.h"
46 @interface SOGoObject(Content)
47 - (NSString *)contentAsString;
50 @interface SoClassSecurityInfo (SOGoAcls)
52 + (id) defaultWebDAVPermissionsMap;
54 - (NSArray *) allPermissions;
55 - (NSArray *) allDAVPermissions;
56 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
57 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
61 @implementation SoClassSecurityInfo (SOGoAcls)
63 + (id) defaultWebDAVPermissionsMap
65 return [NSDictionary dictionaryWithObjectsAndKeys:
66 @"read", SoPerm_AccessContentsInformation,
68 @"bind", SoPerm_AddDocumentsImagesAndFiles,
69 @"unbind", SoPerm_DeleteObjects,
70 @"write-acl", SoPerm_ChangePermissions,
71 @"write-content", SoPerm_ChangeImagesAndFiles,
72 @"read-free-busy", SOGoPerm_FreeBusyLookup,
76 - (NSArray *) allPermissions
78 return [defRoles allKeys];
81 - (NSArray *) allDAVPermissions
83 NSEnumerator *allPermissions;
84 NSMutableArray *davPermissions;
85 NSDictionary *davPermissionsMap;
86 NSString *sopePermission, *davPermission;
88 davPermissions = [NSMutableArray array];
90 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
91 allPermissions = [[self allPermissions] objectEnumerator];
92 sopePermission = [allPermissions nextObject];
93 while (sopePermission)
95 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
96 if (davPermission && ![davPermissions containsObject: davPermission])
97 [davPermissions addObject: davPermission];
98 sopePermission = [allPermissions nextObject];
101 return davPermissions;
104 - (NSArray *) DAVPermissionsForRole: (NSString *) role
106 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
109 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
111 NSEnumerator *allPermissions;
112 NSMutableArray *davPermissions;
113 NSDictionary *davPermissionsMap;
114 NSString *sopePermission, *davPermission;
116 davPermissions = [NSMutableArray array];
118 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
119 allPermissions = [[self allPermissions] objectEnumerator];
120 sopePermission = [allPermissions nextObject];
121 while (sopePermission)
123 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
124 firstObjectCommonWithArray: roles])
127 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
129 && ![davPermissions containsObject: davPermission])
130 [davPermissions addObject: davPermission];
132 sopePermission = [allPermissions nextObject];
135 return davPermissions;
140 @implementation SOGoObject
142 static BOOL kontactGroupDAV = YES;
150 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
153 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
155 // SoClass security declarations
157 // require View permission to access the root (bound to authenticated ...)
158 [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
160 // to allow public access to all contained objects (subkeys)
161 [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
163 // /* require Authenticated role for View and WebDAV */
164 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
165 // asDefaultForPermission: SoPerm_View];
166 // [[self soClassSecurityInfo] declareRole: SoRole_Owner
167 // asDefaultForPermission: SoPerm_WebDAVAccess];
170 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
171 withDAVMethods: (NSString *) firstMethod, ...
174 NSString *aclMethodName;
175 NSString *methodName;
178 va_start (ap, firstMethod);
179 aclMethodName = firstMethod;
180 while (aclMethodName)
182 methodName = [aclMethodName davMethodToObjC];
183 methodSel = NSSelectorFromString (methodName);
184 if (methodSel && [self instancesRespondToSelector: methodSel])
185 [dictionary setObject: methodName
186 forKey: [NSString stringWithFormat: @"{DAV:}%@",
189 NSLog(@"************ method '%@' is still unimplemented!",
191 aclMethodName = va_arg (ap, NSString *);
197 + (NSDictionary *) defaultWebDAVAttributeMap
199 static NSMutableDictionary *map = nil;
203 map = [NSMutableDictionary
204 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
206 [self _fillDictionary: map
207 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
208 @"current-user-privilege-set", @"acl", @"acl-restrictions",
209 @"inherited-acl-set", @"principal-collection-set", nil];
217 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
221 object = [[self alloc] initWithName: _name inContainer: _container];
222 [object autorelease];
227 /* DAV ACL properties */
228 - (NSString *) davOwner
230 return [NSString stringWithFormat: @"%@users/%@",
231 [self rootURLInContext: context],
232 [self ownerInContext: nil]];
235 - (NSString *) davAclRestrictions
237 NSMutableString *restrictions;
239 restrictions = [NSMutableString string];
240 [restrictions appendString: @"<D:grant-only/>"];
241 [restrictions appendString: @"<D:no-invert/>"];
246 - (SOGoDAVSet *) davPrincipalCollectionSet
250 usersUrl = [NSString stringWithFormat: @"%@users",
251 [self rootURLInContext: context]];
253 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
254 ofValuesTaggedAs: @"D:href"];
257 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
259 SOGoAuthenticator *sAuth;
262 SoClassSecurityInfo *sInfo;
263 NSArray *davPermissions;
265 sAuth = [SOGoAuthenticator sharedSOGoAuthenticator];
266 user = [sAuth userInContext: context];
267 roles = [user rolesForObject: self inContext: context];
268 sInfo = [[self class] soClassSecurityInfo];
271 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
273 return [SOGoDAVSet davSetWithArray: davPermissions
274 ofValuesTaggedAs: @"D:privilege"];
277 - (SOGoDAVSet *) davSupportedPrivilegeSet
279 SoClassSecurityInfo *sInfo;
280 NSArray *allPermissions;
282 sInfo = [[self class] soClassSecurityInfo];
284 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
286 return [SOGoDAVSet davSetWithArray: allPermissions
287 ofValuesTaggedAs: @"D:privilege"];
290 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
294 NSMutableString *currentAce;
295 NSMutableArray *davAces;
296 NSString *currentKey, *principal;
297 SOGoDAVSet *privilegesDS;
299 davAces = [NSMutableArray array];
300 keys = [[aclsDictionary allKeys] objectEnumerator];
301 currentKey = [keys nextObject];
304 currentAce = [NSMutableString string];
305 if ([currentKey hasPrefix: @":"])
307 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
308 [currentKey substringFromIndex: 1]];
311 principal = [NSString stringWithFormat: @"%@users/%@",
312 [self rootURLInContext: context],
315 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
319 privileges = [[aclsDictionary objectForKey: currentKey]
320 stringsWithFormat: @"<D:%@/>"];
321 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
322 ofValuesTaggedAs: @"privilege"];
323 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
325 inContext: nil prefixes: nil]];
326 [davAces addObject: currentAce];
327 currentKey = [keys nextObject];
333 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
334 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
338 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
340 [aclsDictionary setObject: perms forKey: @":owner"];
341 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
343 [aclsDictionary setObject: perms forKey: @":authenticated"];
344 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
346 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
349 - (SOGoDAVSet *) davAcl
353 NSMutableDictionary *aclsDictionary;
354 NSDictionary *currentAcl;
355 SoClassSecurityInfo *sInfo;
357 acls = [[self acls] objectEnumerator];
358 aclsDictionary = [NSMutableDictionary dictionary];
359 sInfo = [[self class] soClassSecurityInfo];
361 currentAcl = [acls nextObject];
364 role = [NSArray arrayWithObject: [currentAcl objectForKey: @"role"]];
365 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: role]
366 forKey: [currentAcl objectForKey: @"uid"]];
367 currentAcl = [acls nextObject];
369 [self _appendRolesForPseudoPrincipals: aclsDictionary
370 withClassSecurityInfo: sInfo];
372 return [SOGoDAVSet davSetWithArray:
373 [self _davAcesFromAclsDictionary: aclsDictionary]
374 ofValuesTaggedAs: @"D:ace"];
377 /* end of properties */
379 - (BOOL) doesRetainContainer
384 - (id)initWithName:(NSString *)_name inContainer:(id)_container {
385 if ((self = [self init]))
387 context = [[WOApplication application] context];
389 nameInContainer = [_name copy];
391 [self doesRetainContainer] ? [_container retain] : _container;
400 if ((self = [super init]))
402 nameInContainer = nil;
411 [customOwner release];
412 if ([self doesRetainContainer])
414 [nameInContainer release];
420 - (NSString *)nameInContainer {
421 return nameInContainer;
429 - (void) setOwner: (NSString *) newOwner
431 ASSIGN (customOwner, newOwner);
434 - (NSString *)ownerInContext:(id)_ctx {
435 return ((customOwner)
437 : [[self container] ownerInContext:_ctx]);
442 - (NSArray *)fetchSubfolders {
447 if ((names = [self toManyRelationshipKeys]) == nil)
450 count = [names count];
451 ma = [NSMutableArray arrayWithCapacity:count + 1];
452 for (i = 0; i < count; i++) {
455 folder = [self lookupName: [names objectAtIndex:i]
460 if ([folder isKindOfClass:[NSException class]])
463 [ma addObject:folder];
468 /* looking up shared objects */
470 - (SOGoUserFolder *)lookupUserFolder {
471 if (![container respondsToSelector:_cmd])
474 return [container lookupUserFolder];
476 - (SOGoGroupsFolder *)lookupGroupsFolder {
477 return [[self lookupUserFolder] lookupGroupsFolder];
481 if ([self doesRetainContainer])
488 - (NSException *)delete {
489 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
490 reason:@"delete not yet implemented, sorry ..."];
495 - (id)valueForUndefinedKey:(NSString *)_key {
501 - (NSString *)davDisplayName {
502 return [self nameInContainer];
507 - (id)DELETEAction:(id)_ctx {
510 if ((error = [self delete]) != nil)
513 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
514 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
517 - (id)GETAction:(id)_ctx {
518 // TODO: I guess this should really be done by SOPE (redirect to
524 r = [(WOContext *)_ctx response];
525 rq = [(WOContext *)_ctx request];
527 if ([rq isSoWebDAVRequest]) {
528 if ([self respondsToSelector:@selector(contentAsString)]) {
532 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
535 [r appendContentString:[self contentAsString]];
537 if ((etag = [self davEntityTag]) != nil)
538 [r setHeader:etag forKey:@"etag"];
543 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
544 reason:@"no WebDAV GET support?!"];
548 [r setStatus:302 /* moved */];
549 [r setHeader: [uri composeURLWithAction: @"view"
550 parameters: [rq formValues]
559 - (NSArray *)parseETagList:(NSString *)_c {
564 if ([_c length] == 0)
566 if ([_c isEqualToString:@"*"])
569 etags = [_c componentsSeparatedByString:@","];
570 count = [etags count];
571 ma = [NSMutableArray arrayWithCapacity:count];
572 for (i = 0; i < count; i++) {
575 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
576 #if 0 /* this is non-sense, right? */
577 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
578 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
581 if (etag != nil) [ma addObject:etag];
586 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
588 Only run the request if one of the etags matches the resource etag,
589 usually used to ensure consistent PUTs.
594 if ([_c isEqualToString:@"*"])
595 /* to ensure that the resource exists! */
598 if ((etags = [self parseETagList:_c]) == nil)
600 if ([etags count] == 0) /* no etags to check for? */
603 etag = [self davEntityTag];
604 if ([etag length] == 0) /* has no etag, ignore */
607 if ([etags containsObject:etag]) {
608 [self debugWithFormat:@"etag '%@' matches: %@", etag,
609 [etags componentsJoinedByString:@","]];
610 return nil; /* one etag matches, so continue with request */
613 /* hack for Kontact 3.4 */
615 if (kontactGroupDAV) {
616 WEClientCapabilities *cc;
618 cc = [[(WOContext *)_ctx request] clientCapabilities];
619 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
620 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
622 @"WARNING: applying Kontact 3.4 GroupDAV hack"
623 @" - etag check is disabled!"
624 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
630 // TODO: we might want to return the davEntityTag in the response
631 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
632 [etags componentsJoinedByString:@","]];
633 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
634 reason:@"Precondition Failed"];
637 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
639 If one of the etags is still the same, we can ignore the request.
641 Can be used for PUT to ensure that the object does not exist in the store
642 and for GET to retrieve the content only if if the etag changed.
645 if (![_c isEqualToString:@"*"] &&
646 [[[_ctx request] method] isEqualToString:@"GET"]) {
650 if ((etags = [self parseETagList:_c]) == nil)
652 if ([etags count] == 0) /* no etags to check for? */
655 etag = [self davEntityTag];
656 if ([etag length] == 0) /* has no etag, ignore */
659 if ([etags containsObject:etag]) {
660 [self debugWithFormat:@"etag '%@' matches: %@", etag,
661 [etags componentsJoinedByString:@","]];
662 /* one etag matches, so stop the request */
663 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
664 reason:@"object was not modified"];
671 if ([_c isEqualToString:@"*"])
674 if ((a = [self parseETagList:_c]) == nil)
677 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
682 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
687 if ((rq = [(WOContext *)_ctx request]) == nil)
688 return nil; /* be tolerant - no request, no condition */
690 if ((c = [rq headerForKey:@"if-match"]) != nil) {
691 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
694 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
695 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
706 [self subclassResponsibility: _cmd];
711 - (NSArray *) aclsForUser: (NSString *) uid
713 [self subclassResponsibility: _cmd];
718 - (NSArray *) defaultAclRoles
720 [self subclassResponsibility: _cmd];
725 - (void) setRoles: (NSArray *) roles
726 forUser: (NSString *) uid
728 [self subclassResponsibility: _cmd];
731 - (void) removeAclsForUsers: (NSArray *) users
733 [self subclassResponsibility: _cmd];
738 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
740 [_ms appendFormat:@" name=%@", nameInContainer];
742 [_ms appendFormat:@" container=0x%08X/%@",
743 container, [container valueForKey:@"nameInContainer"]];
746 - (NSString *)description {
749 ms = [NSMutableString stringWithCapacity:64];
750 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
751 [self appendAttributesToDescription:ms];
752 [ms appendString:@">"];
757 @end /* SOGoObject */