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/WOResponse.h>
25 #import <NGObjWeb/WOApplication.h>
26 #import <NGCards/NSDictionary+NGCards.h>
30 #import "NSArray+Utilities.h"
31 #import "NSString+Utilities.h"
33 #import "SOGoPermissions.h"
35 #import "SOGoAclsFolder.h"
36 #import "SOGoAuthenticator.h"
37 #import "SOGoUserFolder.h"
39 #import "SOGoDAVRendererTypes.h"
40 #import "AgenorUserManager.h"
42 #import "SOGoObject.h"
44 @interface SOGoObject(Content)
45 - (NSString *)contentAsString;
48 @interface SoClassSecurityInfo (SOGoAcls)
50 + (id) defaultWebDAVPermissionsMap;
52 - (NSArray *) allPermissions;
53 - (NSArray *) allDAVPermissions;
54 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
55 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
59 @implementation SoClassSecurityInfo (SOGoAcls)
61 + (id) defaultWebDAVPermissionsMap
63 return [NSDictionary dictionaryWithObjectsAndKeys:
64 @"read", SoPerm_AccessContentsInformation,
66 @"bind", SoPerm_AddDocumentsImagesAndFiles,
67 @"unbind", SoPerm_DeleteObjects,
68 @"write-acl", SoPerm_ChangePermissions,
69 @"write-content", SoPerm_ChangeImagesAndFiles,
70 @"read-free-busy", SOGoPerm_FreeBusyLookup,
74 - (NSArray *) allPermissions
76 return [defRoles allKeys];
79 - (NSArray *) allDAVPermissions
81 NSEnumerator *allPermissions;
82 NSMutableArray *davPermissions;
83 NSDictionary *davPermissionsMap;
84 NSString *sopePermission, *davPermission;
86 davPermissions = [NSMutableArray array];
88 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
89 allPermissions = [[self allPermissions] objectEnumerator];
90 sopePermission = [allPermissions nextObject];
91 while (sopePermission)
93 davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
94 if (davPermission && ![davPermissions containsObject: davPermission])
95 [davPermissions addObject: davPermission];
96 sopePermission = [allPermissions nextObject];
99 return davPermissions;
102 - (NSArray *) DAVPermissionsForRole: (NSString *) role
104 return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
107 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
109 NSEnumerator *allPermissions;
110 NSMutableArray *davPermissions;
111 NSDictionary *davPermissionsMap;
112 NSString *sopePermission, *davPermission;
114 davPermissions = [NSMutableArray array];
116 davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
117 allPermissions = [[self allPermissions] objectEnumerator];
118 sopePermission = [allPermissions nextObject];
119 while (sopePermission)
121 if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
122 firstObjectCommonWithArray: roles])
125 = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
127 && ![davPermissions containsObject: davPermission])
128 [davPermissions addObject: davPermission];
130 sopePermission = [allPermissions nextObject];
133 return davPermissions;
138 @implementation SOGoObject
140 static BOOL kontactGroupDAV = YES;
141 static NSTimeZone *serverTimeZone = nil;
151 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
154 [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
156 /* SoClass security declarations */
158 /* require View permission to access the root (bound to authenticated ...) */
159 [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
161 /* to allow public access to all contained objects (subkeys) */
162 [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
164 /* require Authenticated role for View and WebDAV */
165 [[self soClassSecurityInfo] declareRole: SoRole_Owner
166 asDefaultForPermission: SoPerm_View];
167 [[self soClassSecurityInfo] declareRole: SoRole_Owner
168 asDefaultForPermission: SoPerm_WebDAVAccess];
172 tzName = [ud stringForKey: @"SOGoServerTimeZone"];
174 tzName = @"Canada/Eastern";
175 serverTimeZone = [NSTimeZone timeZoneWithName: tzName];
176 [serverTimeZone retain];
180 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
181 withDAVMethods: (NSString *) firstMethod, ...
184 NSString *aclMethodName;
185 NSString *methodName;
188 va_start (ap, firstMethod);
189 aclMethodName = firstMethod;
190 while (aclMethodName)
192 methodName = [aclMethodName davMethodToObjC];
193 methodSel = NSSelectorFromString (methodName);
194 if (methodSel && [self instancesRespondToSelector: methodSel])
195 [dictionary setObject: methodName
196 forKey: [NSString stringWithFormat: @"{DAV:}%@",
199 NSLog(@"************ method '%@' is still unimplemented!",
201 aclMethodName = va_arg (ap, NSString *);
207 + (NSDictionary *) defaultWebDAVAttributeMap
209 static NSMutableDictionary *map = nil;
213 map = [NSMutableDictionary
214 dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
216 [self _fillDictionary: map
217 withDAVMethods: @"owner", @"group", @"supported-privilege-set",
218 @"current-user-privilege-set", @"acl", @"acl-restrictions",
219 @"inherited-acl-set", @"principal-collection-set", nil];
227 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
231 object = [[self alloc] initWithName: _name inContainer: _container];
232 [object autorelease];
237 /* DAV ACL properties */
238 - (NSString *) _principalForUser: (NSString *) user
242 context = [[WOApplication application] context];
244 return [NSString stringWithFormat: @"%@users/%@",
245 [self rootURLInContext: context],
249 - (NSString *) davOwner
251 return [self _principalForUser: [self ownerInContext: nil]];
254 - (NSString *) davAclRestrictions
256 NSMutableString *restrictions;
258 restrictions = [NSMutableString string];
259 [restrictions appendString: @"<D:grant-only/>"];
260 [restrictions appendString: @"<D:no-invert/>"];
265 - (SOGoDAVSet *) davPrincipalCollectionSet
270 context = [[WOApplication application] context];
271 usersUrl = [NSString stringWithFormat: @"%@users",
272 [self rootURLInContext: context]];
274 return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
275 ofValuesTaggedAs: @"D:href"];
278 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
280 SOGoAuthenticator *sAuth;
284 SoClassSecurityInfo *sInfo;
285 NSArray *davPermissions;
287 sAuth = [SOGoAuthenticator sharedSOGoAuthenticator];
288 context = [[WOApplication application] context];
289 user = [sAuth userInContext: context];
290 roles = [user rolesForObject: self inContext: context];
291 sInfo = [[self class] soClassSecurityInfo];
294 = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
296 return [SOGoDAVSet davSetWithArray: davPermissions
297 ofValuesTaggedAs: @"D:privilege"];
300 - (SOGoDAVSet *) davSupportedPrivilegeSet
302 SoClassSecurityInfo *sInfo;
303 NSArray *allPermissions;
305 sInfo = [[self class] soClassSecurityInfo];
307 allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
309 return [SOGoDAVSet davSetWithArray: allPermissions
310 ofValuesTaggedAs: @"D:privilege"];
313 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
317 NSMutableString *currentAce;
318 NSMutableArray *davAces;
319 NSString *currentKey;
320 SOGoDAVSet *privilegesDS;
322 davAces = [NSMutableArray array];
323 keys = [[aclsDictionary allKeys] objectEnumerator];
324 currentKey = [keys nextObject];
327 currentAce = [NSMutableString string];
328 if ([currentKey hasPrefix: @":"])
330 appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
331 [currentKey substringFromIndex: 1]];
334 appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
335 [self _principalForUser: currentKey]];
336 privileges = [[aclsDictionary objectForKey: currentKey]
337 stringsWithFormat: @"<D:%@/>"];
338 privilegesDS = [SOGoDAVSet davSetWithArray: privileges
339 ofValuesTaggedAs: @"privilege"];
340 [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
342 inContext: nil prefixes: nil]];
343 [davAces addObject: currentAce];
344 currentKey = [keys nextObject];
350 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
351 withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
355 perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
357 [aclsDictionary setObject: perms forKey: @":owner"];
358 perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
360 [aclsDictionary setObject: perms forKey: @":authenticated"];
361 perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
363 [aclsDictionary setObject: perms forKey: @":unauthenticated"];
366 - (SOGoDAVSet *) davAcl
370 NSMutableDictionary *aclsDictionary;
371 NSDictionary *currentAcl;
372 SoClassSecurityInfo *sInfo;
374 acls = [[[SOGoAclsFolder aclsFolder] aclsForObject: self] objectEnumerator];
375 aclsDictionary = [NSMutableDictionary dictionary];
376 sInfo = [[self class] soClassSecurityInfo];
378 currentAcl = [acls nextObject];
381 role = [NSArray arrayWithObject: [currentAcl objectForKey: @"role"]];
382 [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: role]
383 forKey: [currentAcl objectForKey: @"uid"]];
384 currentAcl = [acls nextObject];
386 [self _appendRolesForPseudoPrincipals: aclsDictionary
387 withClassSecurityInfo: sInfo];
389 return [SOGoDAVSet davSetWithArray:
390 [self _davAcesFromAclsDictionary: aclsDictionary]
391 ofValuesTaggedAs: @"D:ace"];
394 /* end of properties */
396 - (BOOL)doesRetainContainer {
400 - (id)initWithName:(NSString *)_name inContainer:(id)_container {
401 if ((self = [super init])) {
402 nameInContainer = [_name copy];
404 [self doesRetainContainer] ? [_container retain] : _container;
412 return [self initWithName:nil inContainer:nil];
417 [customOwner release];
418 if ([self doesRetainContainer])
421 [userTimeZone release];
422 [nameInContainer release];
428 - (NSString *)nameInContainer {
429 return nameInContainer;
437 - (void) setOwner: (NSString *) newOwner
439 ASSIGN (customOwner, newOwner);
442 - (NSString *)ownerInContext:(id)_ctx {
443 return ((customOwner)
445 : [[self container] ownerInContext:_ctx]);
450 - (NSArray *)fetchSubfolders {
455 if ((names = [self toManyRelationshipKeys]) == nil)
458 count = [names count];
459 ma = [NSMutableArray arrayWithCapacity:count + 1];
460 for (i = 0; i < count; i++) {
463 folder = [self lookupName: [names objectAtIndex:i]
468 if ([folder isKindOfClass:[NSException class]])
471 [ma addObject:folder];
476 /* looking up shared objects */
478 - (SOGoUserFolder *)lookupUserFolder {
479 if (![container respondsToSelector:_cmd])
482 return [container lookupUserFolder];
484 - (SOGoGroupsFolder *)lookupGroupsFolder {
485 return [[self lookupUserFolder] lookupGroupsFolder];
489 if ([self doesRetainContainer])
496 - (NSException *)delete {
497 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
498 reason:@"delete not yet implemented, sorry ..."];
503 - (id)valueForUndefinedKey:(NSString *)_key {
509 - (NSString *)davDisplayName {
510 return [self nameInContainer];
515 - (id)DELETEAction:(id)_ctx {
518 if ((error = [self delete]) != nil)
521 /* Note: returning 'nil' breaks in SoObjectRequestHandler */
522 return [NSNumber numberWithBool:YES]; /* delete worked out ... */
525 - (id)GETAction:(id)_ctx {
526 // TODO: I guess this should really be done by SOPE (redirect to
532 r = [(WOContext *)_ctx response];
533 rq = [(WOContext *)_ctx request];
535 if ([rq isSoWebDAVRequest]) {
536 if ([self respondsToSelector:@selector(contentAsString)]) {
540 if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
543 [r appendContentString:[self contentAsString]];
545 if ((etag = [self davEntityTag]) != nil)
546 [r setHeader:etag forKey:@"etag"];
551 return [NSException exceptionWithHTTPStatus:501 /* not implemented */
552 reason:@"no WebDAV GET support?!"];
556 [r setStatus:302 /* moved */];
557 [r setHeader: [uri composeURLWithAction: @"view"
558 parameters: [rq formValues]
567 - (NSArray *)parseETagList:(NSString *)_c {
572 if ([_c length] == 0)
574 if ([_c isEqualToString:@"*"])
577 etags = [_c componentsSeparatedByString:@","];
578 count = [etags count];
579 ma = [NSMutableArray arrayWithCapacity:count];
580 for (i = 0; i < count; i++) {
583 etag = [[etags objectAtIndex:i] stringByTrimmingSpaces];
584 #if 0 /* this is non-sense, right? */
585 if ([etag hasPrefix:@"\""] && [etag hasSuffix:@"\""])
586 etag = [etag substringWithRange:NSMakeRange(1, [etag length] - 2)];
589 if (etag != nil) [ma addObject:etag];
594 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
596 Only run the request if one of the etags matches the resource etag,
597 usually used to ensure consistent PUTs.
602 if ([_c isEqualToString:@"*"])
603 /* to ensure that the resource exists! */
606 if ((etags = [self parseETagList:_c]) == nil)
608 if ([etags count] == 0) /* no etags to check for? */
611 etag = [self davEntityTag];
612 if ([etag length] == 0) /* has no etag, ignore */
615 if ([etags containsObject:etag]) {
616 [self debugWithFormat:@"etag '%@' matches: %@", etag,
617 [etags componentsJoinedByString:@","]];
618 return nil; /* one etag matches, so continue with request */
621 /* hack for Kontact 3.4 */
623 if (kontactGroupDAV) {
624 WEClientCapabilities *cc;
626 cc = [[(WOContext *)_ctx request] clientCapabilities];
627 if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
628 if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
630 @"WARNING: applying Kontact 3.4 GroupDAV hack"
631 @" - etag check is disabled!"
632 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
638 // TODO: we might want to return the davEntityTag in the response
639 [self debugWithFormat:@"etag '%@' does not match: %@", etag,
640 [etags componentsJoinedByString:@","]];
641 return [NSException exceptionWithHTTPStatus:412 /* Precondition Failed */
642 reason:@"Precondition Failed"];
645 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
647 If one of the etags is still the same, we can ignore the request.
649 Can be used for PUT to ensure that the object does not exist in the store
650 and for GET to retrieve the content only if if the etag changed.
653 if (![_c isEqualToString:@"*"] &&
654 [[[_ctx request] method] isEqualToString:@"GET"]) {
658 if ((etags = [self parseETagList:_c]) == nil)
660 if ([etags count] == 0) /* no etags to check for? */
663 etag = [self davEntityTag];
664 if ([etag length] == 0) /* has no etag, ignore */
667 if ([etags containsObject:etag]) {
668 [self debugWithFormat:@"etag '%@' matches: %@", etag,
669 [etags componentsJoinedByString:@","]];
670 /* one etag matches, so stop the request */
671 return [NSException exceptionWithHTTPStatus:304 /* Not Modified */
672 reason:@"object was not modified"];
679 if ([_c isEqualToString:@"*"])
682 if ((a = [self parseETagList:_c]) == nil)
685 [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
690 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
695 if ((rq = [(WOContext *)_ctx request]) == nil)
696 return nil; /* be tolerant - no request, no condition */
698 if ((c = [rq headerForKey:@"if-match"]) != nil) {
699 if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
702 if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
703 if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
710 - (NSTimeZone *) serverTimeZone
712 return serverTimeZone;
715 /* TODO: should be moved into SOGoUser */
716 - (NSTimeZone *) userTimeZone
718 NSUserDefaults *userPrefs;
723 context = [[WOApplication application] context];
724 userPrefs = [[context activeUser] userDefaults];
725 userTimeZone = [NSTimeZone
726 timeZoneWithName: [userPrefs stringForKey: @"timezonename"]];
728 userTimeZone = [self serverTimeZone];
729 [userTimeZone retain];
735 - (NSTimeZone *) userTimeZone: (NSString *) username
737 NSUserDefaults *userPrefs;
738 AgenorUserManager *am;
740 am = [AgenorUserManager sharedUserManager];
741 userPrefs = [am getUserDefaultsForUID: username];
742 userTimeZone = [NSTimeZone timeZoneWithName: [userPrefs stringForKey: @"timezonename"]];
744 userTimeZone = [self serverTimeZone];
751 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
753 [_ms appendFormat:@" name=%@", nameInContainer];
755 [_ms appendFormat:@" container=0x%08X/%@",
756 container, [container valueForKey:@"nameInContainer"]];
759 - (NSString *)description {
762 ms = [NSMutableString stringWithCapacity:64];
763 [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
764 [self appendAttributesToDescription:ms];
765 [ms appendString:@">"];
770 @end /* SOGoObject */