~ubuntu-branches/ubuntu/edgy/sope/edgy

« back to all changes in this revision

Viewing changes to sope-appserver/NGObjWeb/SoObjects/SoSecurityManager.m

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Ley
  • Date: 2005-08-19 16:53:31 UTC
  • Revision ID: james.westby@ubuntu.com-20050819165331-hs683wz1osm708pw
Tags: upstream-4.4rc.2
ImportĀ upstreamĀ versionĀ 4.4rc.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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%08X, 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%08X, 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
    if ((sinfo = [soClass soClassSecurityInfo]) == nil) continue;
 
129
    if ((m = [sinfo methodForSelector:_sel])) {
 
130
      BOOL ok;
 
131
      
 
132
      ok = (_object)
 
133
        ? ((BOOL (*)(id, SEL, id))m)(sinfo, _sel, _object)
 
134
        : ((BOOL (*)(id, SEL))m)(sinfo, _sel);
 
135
      if (ok) return sinfo;
 
136
    }
 
137
  }
 
138
  return nil;
 
139
}
 
140
 
 
141
/* validation */
 
142
 
 
143
- (id)authenticatorInContext:(id)_ctx object:(id)_object {
 
144
  id authenticator;
 
145
 
 
146
  if ((authenticator = [_object authenticatorInContext:_ctx]) == nil)
 
147
    authenticator = [[WOApplication application] authenticatorInContext:_ctx];
 
148
  return authenticator;
 
149
}
 
150
- (id<SoUser>)userInContext:(id)_ctx object:(id)_object {
 
151
  id user, authenticator;
 
152
  
 
153
  if ((user = [(WOContext *)_ctx activeUser]) != nil)
 
154
    return [user isNotNull] ? user : nil;
 
155
  
 
156
  authenticator = [self authenticatorInContext:_ctx object:_object];
 
157
    
 
158
  if ((user = [authenticator userInContext:_ctx]) != nil)
 
159
    [(WOContext *)_ctx setActiveUser:user];
 
160
  
 
161
  return [user isNotNull] ? user : nil;
 
162
}
 
163
 
 
164
- (BOOL)isUser:(id<SoUser>)_user ownerOfObject:(id)_obj inContext:(id)_ctx {
 
165
  NSString *objectOwner;
 
166
  
 
167
  if ((objectOwner = [_obj ownerInContext:_ctx]) == nil)
 
168
    return NO;
 
169
  
 
170
  if ([[_user login] isEqualToString:objectOwner])
 
171
    return YES;
 
172
  
 
173
  return NO;
 
174
}
 
175
 
 
176
- (NSException *)validatePermission:(NSString *)_perm
 
177
  onObject:(id)_object 
 
178
  inContext:(id)_ctx
 
179
{
 
180
  NSMutableDictionary *validatedPerms;
 
181
  NSArray             *rolesHavingPermission;
 
182
  SoClassSecurityInfo *sinfo;
 
183
  id<SoUser> user;
 
184
  NSArray      *userRoles;
 
185
  NSEnumerator *e;
 
186
  NSString     *role;
 
187
  
 
188
  if (_perm == nil)
 
189
    return [self missingPermissionException:_perm forObject:_object];
 
190
  
 
191
#if !USE_PERM_CACHE
 
192
  validatedPerms = nil;
 
193
#else
 
194
  // TODO: Bug !! The cache must go on Permission+ObjectID since the
 
195
  //              permission can be set without the ID !
 
196
  
 
197
  /* check the cache */
 
198
  if ((validatedPerms = [_ctx objectForKey:SoPermCache])) {
 
199
    NSException *o;
 
200
  
 
201
    if ((o = [validatedPerms objectForKey:_perm])) {
 
202
      if (debugOn)
 
203
        [self debugWithFormat:@"permission '%@' cached as valid ...", _perm];
 
204
      
 
205
      if ([o isNotNull])
 
206
        /* an exception */
 
207
        return o;
 
208
      return nil;
 
209
    }
 
210
  }
 
211
  else {
 
212
    /* setup cache */
 
213
    validatedPerms = [[NSMutableDictionary alloc] init];
 
214
    [_ctx setObject:validatedPerms forKey:SoPermCache];
 
215
    [validatedPerms autorelease];
 
216
  }
 
217
#endif
 
218
  
 
219
  if (debugOn) {
 
220
    [self debugWithFormat:@"validate permission '%@' on object: %@",
 
221
            _perm, _object];
 
222
  }
 
223
  
 
224
  if ([_perm isSoPublicPermission])
 
225
    /* the object is public */
 
226
    goto found;
 
227
  
 
228
  /* determine the possible roles for the permission */
 
229
  
 
230
  // TODO: check object for policy (currently only default roles are checked)
 
231
  
 
232
  sinfo = [self lookupInfoOfClass:[_object soClass] 
 
233
                condition:@selector(hasDefaultRoleForPermission:)
 
234
                object:_perm];
 
235
  
 
236
  if (sinfo == nil)
 
237
    sinfo = [[_object soClass] soClassSecurityInfo];
 
238
  
 
239
  rolesHavingPermission = [sinfo defaultRolesForPermission:_perm];
 
240
  if (debugOn) {
 
241
    [self debugWithFormat:@"  possible roles for permission '%@': %@",
 
242
            _perm, [rolesHavingPermission componentsJoinedByString:@", "]];
 
243
  }
 
244
  
 
245
  if ([rolesHavingPermission containsObject:SoRole_Anonymous]) {
 
246
    /* is public */
 
247
    [self debugWithFormat:@"  allowed because of anonymous roles."];
 
248
    goto found;
 
249
  }
 
250
  if ([rolesHavingPermission count] == 0) {
 
251
    /* is public */
 
252
    [self debugWithFormat:@"  allowed because no roles are required."];
 
253
    goto found;
 
254
  }
 
255
  
 
256
  /* now retrieve the user that is logged in */
 
257
  
 
258
  if ((user = [self userInContext:_ctx object:_object]) == nil) {
 
259
    /* no user, anonymous */
 
260
    [self debugWithFormat:@"  got no user (=> auth required)."];
 
261
    return [SoAuthRequiredException securityExceptionOnObject:_object
 
262
                                    withAuthenticator:
 
263
                                      [self authenticatorInContext:_ctx 
 
264
                                            object:_object]
 
265
                                    andManager:self];
 
266
  }
 
267
  
 
268
  [self debugWithFormat:@"  got user: %@)", user];
 
269
  
 
270
  /* process user */
 
271
  
 
272
  userRoles = [user rolesForObject:_object inContext:_ctx];
 
273
  [self debugWithFormat:@"    user roles: %@", 
 
274
        [userRoles componentsJoinedByString:@","]];
 
275
  if ([userRoles count] == 0)
 
276
    return [self isPrivateExceptionForObject:_object];
 
277
    
 
278
  /* now check whether the roles subset */
 
279
      
 
280
  e = [userRoles objectEnumerator];
 
281
  while ((role = [e nextObject])) {
 
282
    if ([rolesHavingPermission containsObject:role]) {
 
283
      /* found role ! */
 
284
      break;
 
285
    }
 
286
  }
 
287
    
 
288
  /* if no role was found, check whether the user is the owner */
 
289
    
 
290
  if (role == nil) {
 
291
    if ([rolesHavingPermission containsObject:SoRole_Owner]) {
 
292
      if ([self isUser:user ownerOfObject:_object inContext:_ctx]) {
 
293
        role = SoRole_Owner;
 
294
        [self debugWithFormat:@"    user is owner of object."];
 
295
      }
 
296
      else if ([_object ownerInContext:_ctx] == nil) {
 
297
        role = SoRole_Owner;
 
298
        [self debugWithFormat:@"    object is not owned, grant access."];
 
299
      }
 
300
      else {
 
301
        role = nil;
 
302
        [self debugWithFormat:
 
303
                @"    user is not the owner of object (owner=%@).",
 
304
              [_object ownerInContext:_ctx]];
 
305
      }
 
306
    }
 
307
  }
 
308
    
 
309
  /* check whether a role was finally found */
 
310
    
 
311
  if (role == nil) {
 
312
    [self debugWithFormat:@"    found no matching role."];
 
313
    
 
314
    if ([[user login] isSoAnonymousUserLogin]) {
 
315
      [self debugWithFormat:@"still anonymous, requesting login ..."];
 
316
      return [SoAuthRequiredException securityExceptionOnObject:_object
 
317
                                      withAuthenticator:
 
318
                                        [self authenticatorInContext:_ctx
 
319
                                              object:_object]
 
320
                                      andManager:self];
 
321
    }
 
322
    else {
 
323
      /* 
 
324
         Note: AFAIK Zope will present the user a login panel in any
 
325
         case. IMHO this is not good in practice (you don't change
 
326
         identities very often ;-), and the 403 code has it's value too.
 
327
      */
 
328
      [self debugWithFormat:@"valid user, denying access ..."];
 
329
      return [self isPrivateExceptionForObject:_object];
 
330
    }
 
331
  }
 
332
    
 
333
  [self debugWithFormat:@"    found a valid role: '%@'.", role];
 
334
  
 
335
 found:
 
336
  [self debugWithFormat:@"  successfully validated permission '%@'.", _perm];
 
337
  [validatedPerms setObject:[NSNull null] forKey:_perm];
 
338
  return nil;
 
339
}
 
340
 
 
341
- (NSException *)validateObject:(id)_object inContext:(id)_ctx {
 
342
  /* This methods check how the object itself is protected. */
 
343
  NSMutableArray      *validatedObjects;
 
344
  SoClassSecurityInfo *sinfo;
 
345
  NSString            *perm;
 
346
  NSException *e;
 
347
  
 
348
  if (_object == nil) return nil;
 
349
  
 
350
  /* some objects are always public */
 
351
  if ([_object isPublicInContext:_ctx])
 
352
    return nil;
 
353
  
 
354
  /* check the cache */
 
355
  if ((validatedObjects = [(WOContext *)_ctx objectPermissionCache])) {
 
356
    if ([validatedObjects indexOfObjectIdenticalTo:_object] != NSNotFound)
 
357
      return nil;
 
358
  }
 
359
  else {
 
360
    /* setup cache */
 
361
    validatedObjects = [[NSMutableArray alloc] init];
 
362
    [(WOContext *)_ctx setObjectPermissionCache:validatedObjects];
 
363
    [validatedObjects autorelease];
 
364
  }
 
365
  
 
366
  [self debugWithFormat:@"validate object: %@", _object];
 
367
  
 
368
  /* find the security info which has object protections */
 
369
  sinfo = [self lookupInfoOfClass:[_object soClass] 
 
370
                condition:@selector(hasObjectProtections)
 
371
                object:nil];
 
372
  if (sinfo == nil) {
 
373
    [self debugWithFormat:
 
374
            @"found no security info with object protection for object "
 
375
            @"(rejecting access):\n  object: %@\n  class: %@\n  soclass: %@)", 
 
376
            _object, NSStringFromClass([_object class]), [_object soClass]];
 
377
    return [self isPrivateExceptionForObject:_object];
 
378
  }
 
379
  
 
380
  if ([sinfo isObjectPublic]) {
 
381
    /* object is public ... */
 
382
    [self debugWithFormat:@"  object is public."];
 
383
    [validatedObjects addObject:_object];
 
384
    return nil;
 
385
  }
 
386
  
 
387
  if ([sinfo isObjectPrivate]) {
 
388
    /* object is private ... */
 
389
    [self debugWithFormat:@"  object is private."];
 
390
    return [self isPrivateExceptionForObject:_object];
 
391
  }
 
392
  
 
393
  perm = [sinfo permissionRequiredForObject];
 
394
  if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
 
395
    return e;
 
396
  
 
397
  [self debugWithFormat:@"  successfully validated object (perm=%@).", perm];
 
398
  [validatedObjects addObject:_object];
 
399
  return nil;
 
400
}
 
401
 
 
402
- (NSException *)validateName:(NSString *)_key 
 
403
  ofObject:(id)_object
 
404
  inContext:(id)_ctx
 
405
{
 
406
  /* note: this does not check object-value restrictions */
 
407
  SoClassSecurityInfo *sinfo;
 
408
  NSException *e;
 
409
  NSString    *perm;
 
410
  
 
411
  /* step a: find out permission required for object */
 
412
  
 
413
  if ((e = [self validateObject:_object inContext:_ctx])) {
 
414
    [self debugWithFormat:@"  object did not validate (tried lookup on %@).",
 
415
            _key];
 
416
    return e;
 
417
  }
 
418
  
 
419
  /* step b: find out permission required for key */
 
420
  
 
421
  [self debugWithFormat:@"validate key %@ of object: %@", _key, _object];
 
422
  
 
423
  /* find the security info which has protections for the key */
 
424
  sinfo = [self lookupInfoOfClass:[_object soClass]
 
425
                condition:@selector(hasProtectionsForKey:)
 
426
                object:_key];
 
427
  
 
428
  if (sinfo == nil) {
 
429
    /* found no security for key, so we take the defaults */
 
430
    [self debugWithFormat:@"  found no security info for key (class %@): %@",
 
431
            NSStringFromClass([_object class]), _key];
 
432
    
 
433
    sinfo = [self lookupInfoOfClass:[_object soClass]
 
434
                  condition:@selector(hasDefaultAccessDeclaration)
 
435
                  object:nil];
 
436
    
 
437
    // TODO: search superclasses for one with declared default-access
 
438
    if ([[sinfo defaultAccess] isEqualToString:@"allow"]) {
 
439
      [self debugWithFormat:@"  default is allow ..."];
 
440
      return nil;
 
441
    }
 
442
    return [self isPrivateKeyException:_key ofObject:_object];
 
443
  }
 
444
  
 
445
  if ([sinfo isKeyPublic:_key])
 
446
    return nil;
 
447
  
 
448
  if ([sinfo isKeyPrivate:_key])
 
449
    /* key is private ... */
 
450
    return [self isPrivateKeyException:_key ofObject:_object];
 
451
  
 
452
  perm = [sinfo permissionRequiredForKey:_key];
 
453
  if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
 
454
    return e;
 
455
  
 
456
  [self debugWithFormat:@"  successfully validated key (%@).", _key];
 
457
  return nil;
 
458
}
 
459
 
 
460
- (NSException *)validateValue:(id)_value
 
461
  forName:(NSString *)_key 
 
462
  ofObject:(id)_object
 
463
  inContext:(id)_ctx
 
464
{
 
465
  /* this additionally checks object restrictions of the value */
 
466
  if (_value) {
 
467
    NSException *e;
 
468
    
 
469
    if ((e = [self validateObject:_value inContext:_ctx])) {
 
470
      [self debugWithFormat:@"value (0x%08X,%@) of key %@ didn't validate",
 
471
              _value, NSStringFromClass([_value class]), _key];
 
472
      return e;
 
473
    }
 
474
  }
 
475
  return [self validateName:_key ofObject:_object inContext:_ctx];
 
476
}
 
477
 
 
478
@end /* SoSecurityManager */
 
479
 
 
480
@implementation SoSecurityManager(Logging)
 
481
// Note: this is a category, so that its more difficult to override (of course
 
482
//       still not impossible ...
 
483
 
 
484
- (NSString *)loggingPrefix {
 
485
  return @"[so-security]";
 
486
}
 
487
- (BOOL)isDebuggingEnabled {
 
488
  return debugOn ? YES : NO;
 
489
}
 
490
 
 
491
@end /* SoSecurityManager(Logging) */
 
492
 
 
493
 
 
494
/* public objects */
 
495
 
 
496
@implementation NSObject(Pub)
 
497
- (BOOL)isPublicInContext:(id)_ctx { return NO; }
 
498
@end
 
499
 
 
500
@implementation NSArray(Pub)
 
501
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
 
502
@end
 
503
 
 
504
@implementation NSString(Pub)
 
505
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
 
506
@end
 
507
 
 
508
@implementation NSDictionary(Pub)
 
509
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
 
510
@end
 
511
 
 
512
@implementation NSException(Pub)
 
513
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
 
514
@end
 
515
 
 
516
@implementation NSString(SpecialPermissionChecks)
 
517
 
 
518
- (BOOL)isSoPublicPermission {
 
519
  return [@"<public>" isEqualToString:self];
 
520
}
 
521
- (BOOL)isSoAnonymousUserLogin {
 
522
  return [@"anonymous" isEqualToString:self];
 
523
}
 
524
 
 
525
@end /* NSString(SpecialPermissionChecks) */