]> err.no Git - scalable-opengroupware.org/blob - SoObjects/SOGo/SOGoObject.m
git-svn-id: http://svn.opengroupware.org/SOGo/inverse/trunk@1051 d1b88da0-ebda-0310...
[scalable-opengroupware.org] / SoObjects / SOGo / SOGoObject.m
1 /*
2   Copyright (C) 2004-2005 SKYRIX Software AG
3
4   This file is part of OpenGroupware.org.
5
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
9   later version.
10
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.
15
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
19   02111-1307, USA.
20 */
21
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>
30
31 #import "common.h"
32
33 #import "NSArray+Utilities.h"
34 #import "NSString+Utilities.h"
35
36 #import "SOGoPermissions.h"
37 #import "SOGoUser.h"
38 #import "SOGoAuthenticator.h"
39 #import "SOGoUserFolder.h"
40
41 #import "SOGoDAVRendererTypes.h"
42 #import "AgenorUserManager.h"
43
44 #import "SOGoObject.h"
45
46 @interface SOGoObject(Content)
47 - (NSString *)contentAsString;
48 @end
49
50 @interface SoClassSecurityInfo (SOGoAcls)
51
52 + (id) defaultWebDAVPermissionsMap;
53
54 - (NSArray *) allPermissions;
55 - (NSArray *) allDAVPermissions;
56 - (NSArray *) DAVPermissionsForRole: (NSString *) role;
57 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles;
58
59 @end
60
61 @implementation SoClassSecurityInfo (SOGoAcls)
62
63 + (id) defaultWebDAVPermissionsMap
64 {
65   return [NSDictionary dictionaryWithObjectsAndKeys:
66                          @"read", SoPerm_AccessContentsInformation,
67                        @"read", SoPerm_View, 
68                        @"bind", SoPerm_AddDocumentsImagesAndFiles,
69                        @"unbind", SoPerm_DeleteObjects,
70                        @"write-acl", SoPerm_ChangePermissions,
71                        @"write-content", SoPerm_ChangeImagesAndFiles,
72                        @"read-free-busy", SOGoPerm_FreeBusyLookup,
73                        NULL];
74 }
75
76 - (NSArray *) allPermissions
77 {
78   return [defRoles allKeys];
79 }
80
81 - (NSArray *) allDAVPermissions
82 {
83   NSEnumerator *allPermissions;
84   NSMutableArray *davPermissions;
85   NSDictionary *davPermissionsMap;
86   NSString *sopePermission, *davPermission;
87
88   davPermissions = [NSMutableArray array];
89
90   davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
91   allPermissions = [[self allPermissions] objectEnumerator];
92   sopePermission = [allPermissions nextObject];
93   while (sopePermission)
94     {
95       davPermission = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
96       if (davPermission && ![davPermissions containsObject: davPermission])
97         [davPermissions addObject: davPermission];
98       sopePermission = [allPermissions nextObject];
99     }
100
101   return davPermissions;
102 }
103
104 - (NSArray *) DAVPermissionsForRole: (NSString *) role
105 {
106   return [self DAVPermissionsForRoles: [NSArray arrayWithObject: role]];
107 }
108
109 - (NSArray *) DAVPermissionsForRoles: (NSArray *) roles
110 {
111   NSEnumerator *allPermissions;
112   NSMutableArray *davPermissions;
113   NSDictionary *davPermissionsMap;
114   NSString *sopePermission, *davPermission;
115
116   davPermissions = [NSMutableArray array];
117
118   davPermissionsMap = [[self class] defaultWebDAVPermissionsMap];
119   allPermissions = [[self allPermissions] objectEnumerator];
120   sopePermission = [allPermissions nextObject];
121   while (sopePermission)
122     {
123       if ([[defRoles objectForCaseInsensitiveKey: sopePermission]
124             firstObjectCommonWithArray: roles])
125         {
126           davPermission
127             = [davPermissionsMap objectForCaseInsensitiveKey: sopePermission];
128           if (davPermission
129               && ![davPermissions containsObject: davPermission])
130             [davPermissions addObject: davPermission];
131         }
132       sopePermission = [allPermissions nextObject];
133     }
134
135   return davPermissions;
136 }
137
138 @end
139
140 @implementation SOGoObject
141
142 static BOOL kontactGroupDAV = YES;
143
144 + (int)version {
145   return 0;
146 }
147
148 + (void) initialize
149 {
150   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
151
152   kontactGroupDAV = 
153     [ud boolForKey:@"SOGoDisableKontact34GroupDAVHack"] ? NO : YES;
154
155 //   SoClass security declarations
156   
157 //   require View permission to access the root (bound to authenticated ...)
158   [[self soClassSecurityInfo] declareObjectProtected: SoPerm_View];
159
160 //   to allow public access to all contained objects (subkeys)
161   [[self soClassSecurityInfo] setDefaultAccess: @"allow"];
162   
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];
168 }
169
170 + (void) _fillDictionary: (NSMutableDictionary *) dictionary
171           withDAVMethods: (NSString *) firstMethod, ...
172 {
173   va_list ap;
174   NSString *aclMethodName;
175   NSString *methodName;
176   SEL methodSel;
177
178   va_start (ap, firstMethod);
179   aclMethodName = firstMethod;
180   while (aclMethodName)
181     {
182       methodName = [aclMethodName davMethodToObjC];
183       methodSel = NSSelectorFromString (methodName);
184       if (methodSel && [self instancesRespondToSelector: methodSel])
185         [dictionary setObject: methodName
186                     forKey: [NSString stringWithFormat: @"{DAV:}%@",
187                                       aclMethodName]];
188       else
189         NSLog(@"************ method '%@' is still unimplemented!",
190               methodName);
191       aclMethodName = va_arg (ap, NSString *);
192     }
193
194   va_end (ap);
195 }
196
197 + (NSDictionary *) defaultWebDAVAttributeMap
198 {
199   static NSMutableDictionary *map = nil;
200
201   if (!map)
202     {
203       map = [NSMutableDictionary
204               dictionaryWithDictionary: [super defaultWebDAVAttributeMap]];
205       [map retain];
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];
210     }
211
212   return map;
213 }
214
215 /* containment */
216
217 + (id) objectWithName: (NSString *)_name inContainer:(id)_container
218 {
219   SOGoObject *object;
220
221   object = [[self alloc] initWithName: _name inContainer: _container];
222   [object autorelease];
223
224   return object;
225 }
226
227 /* DAV ACL properties */
228 - (NSString *) davOwner
229 {
230   return [NSString stringWithFormat: @"%@users/%@",
231                    [self rootURLInContext: context],
232                    [self ownerInContext: nil]];
233 }
234
235 - (NSString *) davAclRestrictions
236 {
237   NSMutableString *restrictions;
238
239   restrictions = [NSMutableString string];
240   [restrictions appendString: @"<D:grant-only/>"];
241   [restrictions appendString: @"<D:no-invert/>"];
242
243   return restrictions;
244 }
245
246 - (SOGoDAVSet *) davPrincipalCollectionSet
247 {
248   NSString *usersUrl;
249
250   usersUrl = [NSString stringWithFormat: @"%@users",
251                        [self rootURLInContext: context]];
252
253   return [SOGoDAVSet davSetWithArray: [NSArray arrayWithObject: usersUrl]
254                      ofValuesTaggedAs: @"D:href"];
255 }
256
257 - (SOGoDAVSet *) davCurrentUserPrivilegeSet
258 {
259   SOGoAuthenticator *sAuth;
260   SoUser *user;
261   NSArray *roles;
262   SoClassSecurityInfo *sInfo;
263   NSArray *davPermissions;
264
265   sAuth = [SOGoAuthenticator sharedSOGoAuthenticator];
266   user = [sAuth userInContext: context];
267   roles = [user rolesForObject: self inContext: context];
268   sInfo = [[self class] soClassSecurityInfo];
269
270   davPermissions
271     = [[sInfo DAVPermissionsForRoles: roles] stringsWithFormat: @"<D:%@/>"];
272
273   return [SOGoDAVSet davSetWithArray: davPermissions
274                      ofValuesTaggedAs: @"D:privilege"];
275 }
276
277 - (SOGoDAVSet *) davSupportedPrivilegeSet
278 {
279   SoClassSecurityInfo *sInfo;
280   NSArray *allPermissions;
281
282   sInfo = [[self class] soClassSecurityInfo];
283
284   allPermissions = [[sInfo allDAVPermissions] stringsWithFormat: @"<D:%@/>"];
285
286   return [SOGoDAVSet davSetWithArray: allPermissions
287                      ofValuesTaggedAs: @"D:privilege"];
288 }
289
290 - (NSArray *) _davAcesFromAclsDictionary: (NSDictionary *) aclsDictionary
291 {
292   NSEnumerator *keys;
293   NSArray *privileges;
294   NSMutableString *currentAce;
295   NSMutableArray *davAces;
296   NSString *currentKey, *principal;
297   SOGoDAVSet *privilegesDS;
298
299   davAces = [NSMutableArray array];
300   keys = [[aclsDictionary allKeys] objectEnumerator];
301   currentKey = [keys nextObject];
302   while (currentKey)
303     {
304       currentAce = [NSMutableString string];
305       if ([currentKey hasPrefix: @":"])
306         [currentAce
307           appendFormat: @"<D:principal><D:property><D:%@/></D:property></D:principal>",
308           [currentKey substringFromIndex: 1]];
309       else
310         {
311           principal = [NSString stringWithFormat: @"%@users/%@",
312                                 [self rootURLInContext: context],
313                                 currentKey];
314           [currentAce
315             appendFormat: @"<D:principal><D:href>%@</D:href></D:principal>",
316             principal];
317         }
318
319       privileges = [[aclsDictionary objectForKey: currentKey]
320                      stringsWithFormat: @"<D:%@/>"];
321       privilegesDS = [SOGoDAVSet davSetWithArray: privileges
322                                  ofValuesTaggedAs: @"privilege"];
323       [currentAce appendString: [privilegesDS stringForTag: @"{DAV:}grant"
324                                               rawName: @"grant"
325                                               inContext: nil prefixes: nil]];
326       [davAces addObject: currentAce];
327       currentKey = [keys nextObject];
328     }
329
330   return davAces;
331 }
332
333 - (void) _appendRolesForPseudoPrincipals: (NSMutableDictionary *) aclsDictionary
334                    withClassSecurityInfo: (SoClassSecurityInfo *) sInfo
335 {
336   NSArray *perms;
337
338   perms = [sInfo DAVPermissionsForRole: SoRole_Owner];
339   if ([perms count])
340     [aclsDictionary setObject: perms forKey: @":owner"];
341   perms = [sInfo DAVPermissionsForRole: SoRole_Authenticated];
342   if ([perms count])
343     [aclsDictionary setObject: perms forKey: @":authenticated"];
344   perms = [sInfo DAVPermissionsForRole: SoRole_Anonymous];
345   if ([perms count])
346     [aclsDictionary setObject: perms forKey: @":unauthenticated"];
347 }
348
349 - (SOGoDAVSet *) davAcl
350 {
351   NSArray *role;
352   NSEnumerator *acls;
353   NSMutableDictionary *aclsDictionary;
354   NSDictionary *currentAcl;
355   SoClassSecurityInfo *sInfo;
356
357   acls = [[self acls] objectEnumerator];
358   aclsDictionary = [NSMutableDictionary dictionary];
359   sInfo = [[self class] soClassSecurityInfo];
360
361   currentAcl = [acls nextObject];
362   while (currentAcl)
363     {
364       role = [NSArray arrayWithObject: [currentAcl objectForKey: @"role"]];
365       [aclsDictionary setObject: [sInfo DAVPermissionsForRoles: role]
366                       forKey: [currentAcl objectForKey: @"uid"]];
367       currentAcl = [acls nextObject];
368     }
369   [self _appendRolesForPseudoPrincipals: aclsDictionary
370         withClassSecurityInfo: sInfo];
371
372   return [SOGoDAVSet davSetWithArray:
373                        [self _davAcesFromAclsDictionary: aclsDictionary]
374                      ofValuesTaggedAs: @"D:ace"];
375 }
376
377 /* end of properties */
378
379 - (BOOL) doesRetainContainer
380 {
381   return YES;
382 }
383
384 - (id)initWithName:(NSString *)_name inContainer:(id)_container {
385   if ((self = [self init]))
386     {
387       context = [[WOApplication application] context];
388       [context retain];
389       nameInContainer = [_name copy];
390       container = 
391         [self doesRetainContainer] ? [_container retain] : _container;
392       customOwner = nil;
393     }
394
395   return self;
396 }
397
398 - (id) init
399 {
400   if ((self = [super init]))
401     {
402       nameInContainer = nil;
403       container = nil;
404     }
405
406   return self;
407 }
408
409 - (void)dealloc {
410   [context release];
411   [customOwner release];
412   if ([self doesRetainContainer])
413     [container release];
414   [nameInContainer release];
415   [super dealloc];
416 }
417
418 /* accessors */
419
420 - (NSString *)nameInContainer {
421   return nameInContainer;
422 }
423 - (id)container {
424   return container;
425 }
426
427 /* ownership */
428
429 - (void) setOwner: (NSString *) newOwner
430 {
431   ASSIGN (customOwner, newOwner);
432 }
433
434 - (NSString *)ownerInContext:(id)_ctx {
435   return ((customOwner)
436           ? customOwner
437           : [[self container] ownerInContext:_ctx]);
438 }
439
440 /* hierarchy */
441
442 - (NSArray *)fetchSubfolders {
443   NSMutableArray *ma;
444   NSArray  *names;
445   unsigned i, count;
446   
447   if ((names = [self toManyRelationshipKeys]) == nil)
448     return nil;
449   
450   count = [names count];
451   ma    = [NSMutableArray arrayWithCapacity:count + 1];
452   for (i = 0; i < count; i++) {
453     id folder;
454
455     folder = [self lookupName: [names objectAtIndex:i]
456                    inContext: nil 
457                    acquire: NO];
458     if (folder == nil)
459       continue;
460     if ([folder isKindOfClass:[NSException class]])
461       continue;
462     
463     [ma addObject:folder];
464   }
465   return ma;
466 }
467
468 /* looking up shared objects */
469
470 - (SOGoUserFolder *)lookupUserFolder {
471   if (![container respondsToSelector:_cmd])
472     return nil;
473   
474   return [container lookupUserFolder];
475 }
476 - (SOGoGroupsFolder *)lookupGroupsFolder {
477   return [[self lookupUserFolder] lookupGroupsFolder];
478 }
479
480 - (void)sleep {
481   if ([self doesRetainContainer])
482     [container release];
483   container = nil;
484 }
485
486 /* operations */
487
488 - (NSException *)delete {
489   return [NSException exceptionWithHTTPStatus:501 /* not implemented */
490                       reason:@"delete not yet implemented, sorry ..."];
491 }
492
493 /* KVC hacks */
494
495 - (id)valueForUndefinedKey:(NSString *)_key {
496   return nil;
497 }
498
499 /* WebDAV */
500
501 - (NSString *)davDisplayName {
502   return [self nameInContainer];
503 }
504
505 /* actions */
506
507 - (id)DELETEAction:(id)_ctx {
508   NSException *error;
509
510   if ((error = [self delete]) != nil)
511     return error;
512   
513   /* Note: returning 'nil' breaks in SoObjectRequestHandler */
514   return [NSNumber numberWithBool:YES]; /* delete worked out ... */
515 }
516
517 - (id)GETAction:(id)_ctx {
518   // TODO: I guess this should really be done by SOPE (redirect to
519   //       default method)
520   WORequest  *rq;
521   WOResponse *r;
522   NSString *uri;
523   
524   r  = [(WOContext *)_ctx response];
525   rq = [(WOContext *)_ctx request];
526   
527   if ([rq isSoWebDAVRequest]) {
528     if ([self respondsToSelector:@selector(contentAsString)]) {
529       NSException *error;
530       id etag;
531       
532       if ((error = [self matchesRequestConditionInContext:_ctx]) != nil)
533         return error;
534       
535       [r appendContentString:[self contentAsString]];
536       
537       if ((etag = [self davEntityTag]) != nil)
538         [r setHeader:etag forKey:@"etag"];
539
540       return r;
541     }
542     
543     return [NSException exceptionWithHTTPStatus:501 /* not implemented */
544                         reason:@"no WebDAV GET support?!"];
545   }
546   
547   uri = [rq uri];
548   [r setStatus:302 /* moved */];
549   [r setHeader: [uri composeURLWithAction: @"view"
550                      parameters: [rq formValues]
551                      andHash: NO]
552      forKey:@"location"];
553
554   return r;
555 }
556
557 /* etag support */
558
559 - (NSArray *)parseETagList:(NSString *)_c {
560   NSMutableArray *ma;
561   NSArray  *etags;
562   unsigned i, count;
563   
564   if ([_c length] == 0)
565     return nil;
566   if ([_c isEqualToString:@"*"])
567     return nil;
568   
569   etags = [_c componentsSeparatedByString:@","];
570   count = [etags count];
571   ma    = [NSMutableArray arrayWithCapacity:count];
572   for (i = 0; i < count; i++) {
573     NSString *etag;
574     
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)];
579 #endif
580     
581     if (etag != nil) [ma addObject:etag];
582   }
583   return ma;
584 }
585
586 - (NSException *)checkIfMatchCondition:(NSString *)_c inContext:(id)_ctx {
587   /* 
588      Only run the request if one of the etags matches the resource etag,
589      usually used to ensure consistent PUTs.
590   */
591   NSArray  *etags;
592   NSString *etag;
593   
594   if ([_c isEqualToString:@"*"])
595     /* to ensure that the resource exists! */
596     return nil;
597   
598   if ((etags = [self parseETagList:_c]) == nil)
599     return nil;
600   if ([etags count] == 0) /* no etags to check for? */
601     return nil;
602   
603   etag = [self davEntityTag];
604   if ([etag length] == 0) /* has no etag, ignore */
605     return nil;
606   
607   if ([etags containsObject:etag]) {
608     [self debugWithFormat:@"etag '%@' matches: %@", etag, 
609           [etags componentsJoinedByString:@","]];
610     return nil; /* one etag matches, so continue with request */
611   }
612
613   /* hack for Kontact 3.4 */
614   
615   if (kontactGroupDAV) {
616     WEClientCapabilities *cc;
617     
618     cc = [[(WOContext *)_ctx request] clientCapabilities];
619     if ([[cc userAgentType] isEqualToString:@"Konqueror"]) {
620       if ([cc majorVersion] == 3 && [cc minorVersion] == 4) {
621         [self logWithFormat:
622                 @"WARNING: applying Kontact 3.4 GroupDAV hack"
623                 @" - etag check is disabled!"
624                 @" (can be enabled using 'ZSDisableKontact34GroupDAVHack')"];
625         return nil;
626       }
627     }
628   }
629   
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"];
635 }
636
637 - (NSException *)checkIfNoneMatchCondition:(NSString *)_c inContext:(id)_ctx {
638   /*
639     If one of the etags is still the same, we can ignore the request.
640     
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.
643   */
644   
645   if (![_c isEqualToString:@"*"] && 
646       [[[_ctx request] method] isEqualToString:@"GET"]) {
647     NSString *etag;
648     NSArray  *etags;
649     
650     if ((etags = [self parseETagList:_c]) == nil)
651       return nil;
652     if ([etags count] == 0) /* no etags to check for? */
653       return nil;
654     
655     etag = [self davEntityTag];
656     if ([etag length] == 0) /* has no etag, ignore */
657       return nil;
658     
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"];
665     }
666     
667     return nil;
668   }
669   
670 #if 0
671   if ([_c isEqualToString:@"*"])
672     return nil;
673   
674   if ((a = [self parseETagList:_c]) == nil)
675     return nil;
676 #else
677   [self logWithFormat:@"TODO: implement if-none-match for etag: '%@'", _c];
678 #endif
679   return nil;
680 }
681
682 - (NSException *)matchesRequestConditionInContext:(id)_ctx {
683   NSException *error;
684   WORequest *rq;
685   NSString  *c;
686   
687   if ((rq = [(WOContext *)_ctx request]) == nil)
688     return nil; /* be tolerant - no request, no condition */
689   
690   if ((c = [rq headerForKey:@"if-match"]) != nil) {
691     if ((error = [self checkIfMatchCondition:c inContext:_ctx]) != nil)
692       return error;
693   }
694   if ((c = [rq headerForKey:@"if-none-match"]) != nil) {
695     if ((error = [self checkIfNoneMatchCondition:c inContext:_ctx]) != nil)
696       return error;
697   }
698   
699   return nil;
700 }
701
702 /* acls */
703
704 - (NSArray *) acls
705 {
706   [self subclassResponsibility: _cmd];
707
708   return nil;
709 }
710
711 - (NSArray *) aclsForUser: (NSString *) uid
712 {
713   [self subclassResponsibility: _cmd];
714
715   return nil;
716 }
717
718 - (NSArray *) defaultAclRoles
719 {
720   [self subclassResponsibility: _cmd];
721
722   return nil;
723 }
724
725 - (void) setRoles: (NSArray *) roles
726           forUser: (NSString *) uid
727 {
728   [self subclassResponsibility: _cmd];
729 }
730
731 - (void) removeAclsForUsers: (NSArray *) users
732 {
733   [self subclassResponsibility: _cmd];
734 }
735
736 /* description */
737
738 - (void)appendAttributesToDescription:(NSMutableString *)_ms {
739   if (nameInContainer) 
740     [_ms appendFormat:@" name=%@", nameInContainer];
741   if (container)
742     [_ms appendFormat:@" container=0x%08X/%@", 
743          container, [container valueForKey:@"nameInContainer"]];
744 }
745
746 - (NSString *)description {
747   NSMutableString *ms;
748
749   ms = [NSMutableString stringWithCapacity:64];
750   [ms appendFormat:@"<0x%08X[%@]:", self, NSStringFromClass([self class])];
751   [self appendAttributesToDescription:ms];
752   [ms appendString:@">"];
753
754   return ms;
755 }
756
757 @end /* SOGoObject */