]> err.no Git - sope/blob - sope-appserver/NGObjWeb/SoObjects/SoSecurityManager.m
use %p for pointer formats, fixed gcc 4.1 warnings, minor code improvs
[sope] / sope-appserver / NGObjWeb / SoObjects / SoSecurityManager.m
1 /*
2   Copyright (C) 2002-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE 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   SOPE 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 SOPE; 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 #include "SoSecurityManager.h"
23 #include "SoObject.h"
24 #include "SoClass.h"
25 #include "SoClassSecurityInfo.h"
26 #include "SoPermissions.h"
27 #include "SoUser.h"
28 #include "SoSecurityException.h"
29 #include "WOContext+SoObjects.h"
30 #include <NGObjWeb/WOResponse.h>
31 #include <NGObjWeb/WOApplication.h>
32 #include "common.h"
33
34 @interface NSObject(WOAppAuth)
35 - (BOOL)isPublicInContext:(id)_ctx;
36 @end
37
38 @interface NSObject(UserDB)
39 - (id)userInContext:(WOContext *)_ctx;
40 @end
41
42 @interface NSString(SpecialPermissionChecks)
43 - (BOOL)isSoPublicPermission;
44 - (BOOL)isSoAnonymousUserLogin;
45 @end
46
47 #if USE_PERM_CACHE
48 static NSString *SoPermCache      = @"__validatedperms";
49 #endif
50
51 @implementation SoSecurityManager
52
53 static int debugOn = -1;
54
55 + (void)initialize {
56   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
57   
58   debugOn = [ud boolForKey:@"SoSecurityManagerDebugEnabled"] ? 1 : 0;
59 }
60
61 + (id)sharedSecurityManager {
62   static SoSecurityManager *sharedManager = nil; // THREAD
63   
64   if (sharedManager == nil)
65     sharedManager = [[SoSecurityManager alloc] init];
66   return sharedManager;
67 }
68
69 - (void)dealloc {
70   [super dealloc];
71 }
72
73 /* exceptions */
74
75 - (NSException *)makeExceptionForObject:(id)_obj reason:(NSString *)_r {
76   NSException *e;
77   if (_obj == nil) return nil;
78   e = [SoAccessDeniedException securityExceptionOnObject:_obj
79                                withAuthenticator:nil
80                                andManager:self];
81   if ([_r length] > 0) [e setReason:_r];
82   return e;
83 }
84
85 - (NSException *)isPrivateExceptionForObject:(id)_object {
86   NSString *r;
87   
88   r = [NSString stringWithFormat:@"tried to access private object "
89                 @"(0x%p, SoClass=%@)",
90                 _object, [[_object soClass] className]];
91   return [self makeExceptionForObject:_object reason:r];
92 }
93 - (NSException *)missingPermissionException:(NSString *)_perm 
94   forObject:(id)_object
95 {
96   NSString *r;
97   
98   r = [NSString stringWithFormat:@"missing permission '%@' on object "
99                 @"(0x%p, SoClass=%@)",
100                 _perm, _object, [[_object soClass] className]];
101   return [self makeExceptionForObject:_object  reason:r];
102 }
103
104 - (NSException *)isPrivateKeyException:(NSString *)_key ofObject:(id)_object {
105   NSException *e;
106   NSString    *s;
107   
108   s = [[NSString alloc] initWithFormat:
109                           @"tried to access private key '%@' of object: %@",
110                           _key, _object];
111   e = [self makeExceptionForObject:_object reason:s];
112   [s release];
113   return e;
114 }
115
116 /* secinfo lookup */
117
118 - (SoClassSecurityInfo *)lookupInfoOfClass:(SoClass *)_soClass
119   condition:(SEL)_sel
120   object:(id)_object
121 {
122   SoClass *soClass;
123   
124   for (soClass = _soClass; soClass; soClass = [soClass soSuperClass]) {
125     SoClassSecurityInfo *sinfo;
126     IMP m;
127
128     // [self logWithFormat:@"CHECK CLASS: %@", soClass];
129     
130     if ((sinfo = [soClass soClassSecurityInfo]) == nil) continue;
131     if ((m = [sinfo methodForSelector:_sel])) {
132       BOOL ok;
133       
134       ok = (_object)
135         ? ((BOOL (*)(id, SEL, id))m)(sinfo, _sel, _object)
136         : ((BOOL (*)(id, SEL))m)(sinfo, _sel);
137       if (ok) return sinfo;
138     }
139   }
140   return nil;
141 }
142
143 /* validation */
144
145 - (id)authenticatorInContext:(id)_ctx object:(id)_object {
146   id authenticator;
147
148   if ((authenticator = [_object authenticatorInContext:_ctx]) == nil)
149     authenticator = [[WOApplication application] authenticatorInContext:_ctx];
150   return authenticator;
151 }
152 - (id<SoUser>)userInContext:(id)_ctx object:(id)_object {
153   id user, authenticator;
154   
155   if ((user = [(WOContext *)_ctx activeUser]) != nil)
156     return [user isNotNull] ? user : nil;
157   
158   authenticator = [self authenticatorInContext:_ctx object:_object];
159     
160   if ((user = [authenticator userInContext:_ctx]) != nil)
161     [(WOContext *)_ctx setActiveUser:user];
162   
163   return [user isNotNull] ? user : nil;
164 }
165
166 - (BOOL)isUser:(id<SoUser>)_user ownerOfObject:(id)_obj inContext:(id)_ctx {
167   NSString *objectOwner;
168   
169   if ((objectOwner = [_obj ownerInContext:_ctx]) == nil)
170     return NO;
171   
172   if ([[_user login] isEqualToString:objectOwner])
173     return YES;
174   
175   return NO;
176 }
177
178 - (NSException *)validatePermission:(NSString *)_perm
179   onObject:(id)_object 
180   inContext:(id)_ctx
181 {
182   NSMutableDictionary *validatedPerms;
183   NSArray             *rolesHavingPermission;
184   SoClassSecurityInfo *sinfo;
185   id<SoUser> user;
186   NSArray      *userRoles;
187   NSEnumerator *e;
188   NSString     *role;
189   
190   if (_perm == nil)
191     return [self missingPermissionException:_perm forObject:_object];
192   
193 #if !USE_PERM_CACHE
194   validatedPerms = nil;
195 #else
196   // TODO: Bug !! The cache must go on Permission+ObjectID since the
197   //              permission can be set without the ID !
198   
199   /* check the cache */
200   if ((validatedPerms = [_ctx objectForKey:SoPermCache])) {
201     NSException *o;
202   
203     if ((o = [validatedPerms objectForKey:_perm])) {
204       if (debugOn)
205         [self debugWithFormat:@"permission '%@' cached as valid ...", _perm];
206       
207       if ([o isNotNull])
208         /* an exception */
209         return o;
210       return nil;
211     }
212   }
213   else {
214     /* setup cache */
215     validatedPerms = [[NSMutableDictionary alloc] init];
216     [_ctx setObject:validatedPerms forKey:SoPermCache];
217     [validatedPerms autorelease];
218   }
219 #endif
220   
221   if (debugOn) {
222     [self debugWithFormat:@"validate permission '%@' on object: %@",
223             _perm, _object];
224   }
225   
226   if ([_perm isSoPublicPermission])
227     /* the object is public */
228     goto found;
229   
230   /* determine the possible roles for the permission */
231   
232   // TODO: check object for policy (currently only default roles are checked)
233   
234   sinfo = [self lookupInfoOfClass:[_object soClass] 
235                 condition:@selector(hasDefaultRoleForPermission:)
236                 object:_perm];
237   
238   if (sinfo == nil)
239     sinfo = [[_object soClass] soClassSecurityInfo];
240   
241   rolesHavingPermission = [sinfo defaultRolesForPermission:_perm];
242   if (debugOn) {
243     [self debugWithFormat:@"  possible roles for permission '%@': %@",
244             _perm, [rolesHavingPermission componentsJoinedByString:@", "]];
245   }
246   
247   if ([rolesHavingPermission containsObject:SoRole_Anonymous]) {
248     /* is public */
249     [self debugWithFormat:@"  allowed because of anonymous roles."];
250     goto found;
251   }
252   if ([rolesHavingPermission count] == 0) {
253     /* is public */
254     [self debugWithFormat:@"  allowed because no roles are required."];
255     goto found;
256   }
257   
258   /* now retrieve the user that is logged in */
259   
260   if ((user = [self userInContext:_ctx object:_object]) == nil) {
261     /* no user, anonymous */
262     [self debugWithFormat:@"  got no user (=> auth required)."];
263     return [SoAuthRequiredException securityExceptionOnObject:_object
264                                     withAuthenticator:
265                                       [self authenticatorInContext:_ctx 
266                                             object:_object]
267                                     andManager:self];
268   }
269   
270   [self debugWithFormat:@"  got user: %@)", user];
271   
272   /* process user */
273   
274   userRoles = [user rolesForObject:_object inContext:_ctx];
275   [self debugWithFormat:@"    user roles: %@", 
276         [userRoles componentsJoinedByString:@","]];
277   if ([userRoles count] == 0)
278     return [self isPrivateExceptionForObject:_object];
279     
280   /* now check whether the roles subset */
281       
282   e = [userRoles objectEnumerator];
283   while ((role = [e nextObject])) {
284     if ([rolesHavingPermission containsObject:role]) {
285       /* found role ! */
286       break;
287     }
288   }
289     
290   /* if no role was found, check whether the user is the owner */
291     
292   if (role == nil) {
293     if ([rolesHavingPermission containsObject:SoRole_Owner]) {
294       if ([self isUser:user ownerOfObject:_object inContext:_ctx]) {
295         role = SoRole_Owner;
296         [self debugWithFormat:@"    user is owner of object."];
297       }
298       else if ([_object ownerInContext:_ctx] == nil) {
299         role = SoRole_Owner;
300         [self debugWithFormat:@"    object is not owned, grant access."];
301       }
302       else {
303         role = nil;
304         [self debugWithFormat:
305                 @"    user is not the owner of object (owner=%@).",
306               [_object ownerInContext:_ctx]];
307       }
308     }
309   }
310     
311   /* check whether a role was finally found */
312     
313   if (role == nil) {
314     [self debugWithFormat:@"    found no matching role."];
315     
316     if ([[user login] isSoAnonymousUserLogin]) {
317       [self debugWithFormat:@"still anonymous, requesting login ..."];
318       return [SoAuthRequiredException securityExceptionOnObject:_object
319                                       withAuthenticator:
320                                         [self authenticatorInContext:_ctx
321                                               object:_object]
322                                       andManager:self];
323     }
324     else {
325       /* 
326          Note: AFAIK Zope will present the user a login panel in any
327          case. IMHO this is not good in practice (you don't change
328          identities very often ;-), and the 403 code has it's value too.
329       */
330       [self debugWithFormat:@"valid user, denying access ..."];
331       return [self isPrivateExceptionForObject:_object];
332     }
333   }
334     
335   [self debugWithFormat:@"    found a valid role: '%@'.", role];
336   
337  found:
338   [self debugWithFormat:@"  successfully validated permission '%@'.", _perm];
339   [validatedPerms setObject:[NSNull null] forKey:_perm];
340   return nil;
341 }
342
343 - (NSException *)validateObject:(id)_object inContext:(id)_ctx {
344   /* This methods check how the object itself is protected. */
345   NSMutableArray      *validatedObjects;
346   SoClassSecurityInfo *sinfo;
347   NSString            *perm;
348   NSException *e;
349   
350   if (_object == nil) return nil;
351   
352   /* some objects are always public */
353   if ([_object isPublicInContext:_ctx])
354     return nil;
355   
356   /* check the cache */
357   if ((validatedObjects = [(WOContext *)_ctx objectPermissionCache])) {
358     if ([validatedObjects indexOfObjectIdenticalTo:_object] != NSNotFound)
359       return nil;
360   }
361   else {
362     /* setup cache */
363     validatedObjects = [[NSMutableArray alloc] init];
364     [(WOContext *)_ctx setObjectPermissionCache:validatedObjects];
365     [validatedObjects autorelease];
366   }
367   
368   [self debugWithFormat:@"validate object: %@", _object];
369   
370   /* find the security info which has object protections */
371   sinfo = [self lookupInfoOfClass:[_object soClass] 
372                 condition:@selector(hasObjectProtections)
373                 object:nil];
374   if (sinfo == nil) {
375     [self debugWithFormat:
376             @"found no security info with object protection for object "
377             @"(rejecting access):\n  object: %@\n  class: %@\n  soclass: %@)", 
378             _object, NSStringFromClass([_object class]), [_object soClass]];
379     return [self isPrivateExceptionForObject:_object];
380   }
381   
382   if ([sinfo isObjectPublic]) {
383     /* object is public ... */
384     [self debugWithFormat:@"  object is public."];
385     [validatedObjects addObject:_object];
386     return nil;
387   }
388   
389   if ([sinfo isObjectPrivate]) {
390     /* object is private ... */
391     [self debugWithFormat:@"  object is private."];
392     return [self isPrivateExceptionForObject:_object];
393   }
394   
395   perm = [sinfo permissionRequiredForObject];
396   if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
397     return e;
398   
399   [self debugWithFormat:@"  successfully validated object (perm=%@).", perm];
400   [validatedObjects addObject:_object];
401   return nil;
402 }
403
404 - (NSException *)validateName:(NSString *)_key 
405   ofObject:(id)_object
406   inContext:(id)_ctx
407 {
408   /* note: this does not check object-value restrictions */
409   SoClassSecurityInfo *sinfo;
410   NSException *e;
411   NSString    *perm;
412   
413   /* step a: find out permission required for object */
414   
415   if ((e = [self validateObject:_object inContext:_ctx])) {
416     [self debugWithFormat:@"  object did not validate (tried lookup on %@).",
417             _key];
418     return e;
419   }
420   
421   /* step b: find out permission required for key */
422   
423   [self debugWithFormat:@"validate key %@ of object: %@", _key, _object];
424   
425   /* find the security info which has protections for the key */
426   sinfo = [self lookupInfoOfClass:[_object soClass]
427                 condition:@selector(hasProtectionsForKey:)
428                 object:_key];
429   
430   if (sinfo == nil) {
431     /* found no security for key, so we take the defaults */
432     [self debugWithFormat:@"  found no security info for key (class %@): %@",
433             NSStringFromClass([_object class]), _key];
434     
435     sinfo = [self lookupInfoOfClass:[_object soClass]
436                   condition:@selector(hasDefaultAccessDeclaration)
437                   object:nil];
438     
439     // TODO: search superclasses for one with declared default-access
440     if ([[sinfo defaultAccess] isEqualToString:@"allow"]) {
441       [self debugWithFormat:@"  default is allow ..."];
442       return nil;
443     }
444     return [self isPrivateKeyException:_key ofObject:_object];
445   }
446   
447   if ([sinfo isKeyPublic:_key])
448     return nil;
449   
450   if ([sinfo isKeyPrivate:_key])
451     /* key is private ... */
452     return [self isPrivateKeyException:_key ofObject:_object];
453   
454   perm = [sinfo permissionRequiredForKey:_key];
455   if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
456     return e;
457   
458   [self debugWithFormat:@"  successfully validated key (%@).", _key];
459   return nil;
460 }
461
462 - (NSException *)validateValue:(id)_value
463   forName:(NSString *)_key 
464   ofObject:(id)_object
465   inContext:(id)_ctx
466 {
467   /* this additionally checks object restrictions of the value */
468   if (_value) {
469     NSException *e;
470     
471     if ((e = [self validateObject:_value inContext:_ctx])) {
472       [self debugWithFormat:@"value (0x%p,%@) of key %@ didn't validate",
473               _value, NSStringFromClass([_value class]), _key];
474       return e;
475     }
476   }
477   return [self validateName:_key ofObject:_object inContext:_ctx];
478 }
479
480 @end /* SoSecurityManager */
481
482 @implementation SoSecurityManager(Logging)
483 // Note: this is a category, so that its more difficult to override (of course
484 //       still not impossible ...
485
486 - (NSString *)loggingPrefix {
487   return @"[so-security]";
488 }
489 - (BOOL)isDebuggingEnabled {
490   return debugOn ? YES : NO;
491 }
492
493 @end /* SoSecurityManager(Logging) */
494
495
496 /* public objects */
497
498 @implementation NSObject(Pub)
499 - (BOOL)isPublicInContext:(id)_ctx { return NO; }
500 @end
501
502 @implementation NSArray(Pub)
503 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
504 @end
505
506 @implementation NSString(Pub)
507 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
508 @end
509
510 @implementation NSDictionary(Pub)
511 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
512 @end
513
514 @implementation NSException(Pub)
515 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
516 @end
517
518 @implementation NSString(SpecialPermissionChecks)
519
520 - (BOOL)isSoPublicPermission {
521   return [@"<public>" isEqualToString:self];
522 }
523 - (BOOL)isSoAnonymousUserLogin {
524   return [@"anonymous" isEqualToString:self];
525 }
526
527 @end /* NSString(SpecialPermissionChecks) */