2
Copyright (C) 2002-2005 SKYRIX Software AG
4
This file is part of SOPE.
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
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.
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
22
#include "SoSecurityManager.h"
25
#include "SoClassSecurityInfo.h"
26
#include "SoPermissions.h"
28
#include "SoSecurityException.h"
29
#include "WOContext+SoObjects.h"
30
#include <NGObjWeb/WOResponse.h>
31
#include <NGObjWeb/WOApplication.h>
34
@interface NSObject(WOAppAuth)
35
- (BOOL)isPublicInContext:(id)_ctx;
38
@interface NSObject(UserDB)
39
- (id)userInContext:(WOContext *)_ctx;
42
@interface NSString(SpecialPermissionChecks)
43
- (BOOL)isSoPublicPermission;
44
- (BOOL)isSoAnonymousUserLogin;
48
static NSString *SoPermCache = @"__validatedperms";
51
@implementation SoSecurityManager
53
static int debugOn = -1;
56
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
58
debugOn = [ud boolForKey:@"SoSecurityManagerDebugEnabled"] ? 1 : 0;
61
+ (id)sharedSecurityManager {
62
static SoSecurityManager *sharedManager = nil; // THREAD
64
if (sharedManager == nil)
65
sharedManager = [[SoSecurityManager alloc] init];
75
- (NSException *)makeExceptionForObject:(id)_obj reason:(NSString *)_r {
77
if (_obj == nil) return nil;
78
e = [SoAccessDeniedException securityExceptionOnObject:_obj
81
if ([_r length] > 0) [e setReason:_r];
85
- (NSException *)isPrivateExceptionForObject:(id)_object {
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];
93
- (NSException *)missingPermissionException:(NSString *)_perm
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];
104
- (NSException *)isPrivateKeyException:(NSString *)_key ofObject:(id)_object {
108
s = [[NSString alloc] initWithFormat:
109
@"tried to access private key '%@' of object: %@",
111
e = [self makeExceptionForObject:_object reason:s];
118
- (SoClassSecurityInfo *)lookupInfoOfClass:(SoClass *)_soClass
124
for (soClass = _soClass; soClass; soClass = [soClass soSuperClass]) {
125
SoClassSecurityInfo *sinfo;
128
if ((sinfo = [soClass soClassSecurityInfo]) == nil) continue;
129
if ((m = [sinfo methodForSelector:_sel])) {
133
? ((BOOL (*)(id, SEL, id))m)(sinfo, _sel, _object)
134
: ((BOOL (*)(id, SEL))m)(sinfo, _sel);
135
if (ok) return sinfo;
143
- (id)authenticatorInContext:(id)_ctx object:(id)_object {
146
if ((authenticator = [_object authenticatorInContext:_ctx]) == nil)
147
authenticator = [[WOApplication application] authenticatorInContext:_ctx];
148
return authenticator;
150
- (id<SoUser>)userInContext:(id)_ctx object:(id)_object {
151
id user, authenticator;
153
if ((user = [(WOContext *)_ctx activeUser]) != nil)
154
return [user isNotNull] ? user : nil;
156
authenticator = [self authenticatorInContext:_ctx object:_object];
158
if ((user = [authenticator userInContext:_ctx]) != nil)
159
[(WOContext *)_ctx setActiveUser:user];
161
return [user isNotNull] ? user : nil;
164
- (BOOL)isUser:(id<SoUser>)_user ownerOfObject:(id)_obj inContext:(id)_ctx {
165
NSString *objectOwner;
167
if ((objectOwner = [_obj ownerInContext:_ctx]) == nil)
170
if ([[_user login] isEqualToString:objectOwner])
176
- (NSException *)validatePermission:(NSString *)_perm
180
NSMutableDictionary *validatedPerms;
181
NSArray *rolesHavingPermission;
182
SoClassSecurityInfo *sinfo;
189
return [self missingPermissionException:_perm forObject:_object];
192
validatedPerms = nil;
194
// TODO: Bug !! The cache must go on Permission+ObjectID since the
195
// permission can be set without the ID !
197
/* check the cache */
198
if ((validatedPerms = [_ctx objectForKey:SoPermCache])) {
201
if ((o = [validatedPerms objectForKey:_perm])) {
203
[self debugWithFormat:@"permission '%@' cached as valid ...", _perm];
213
validatedPerms = [[NSMutableDictionary alloc] init];
214
[_ctx setObject:validatedPerms forKey:SoPermCache];
215
[validatedPerms autorelease];
220
[self debugWithFormat:@"validate permission '%@' on object: %@",
224
if ([_perm isSoPublicPermission])
225
/* the object is public */
228
/* determine the possible roles for the permission */
230
// TODO: check object for policy (currently only default roles are checked)
232
sinfo = [self lookupInfoOfClass:[_object soClass]
233
condition:@selector(hasDefaultRoleForPermission:)
237
sinfo = [[_object soClass] soClassSecurityInfo];
239
rolesHavingPermission = [sinfo defaultRolesForPermission:_perm];
241
[self debugWithFormat:@" possible roles for permission '%@': %@",
242
_perm, [rolesHavingPermission componentsJoinedByString:@", "]];
245
if ([rolesHavingPermission containsObject:SoRole_Anonymous]) {
247
[self debugWithFormat:@" allowed because of anonymous roles."];
250
if ([rolesHavingPermission count] == 0) {
252
[self debugWithFormat:@" allowed because no roles are required."];
256
/* now retrieve the user that is logged in */
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
263
[self authenticatorInContext:_ctx
268
[self debugWithFormat:@" got user: %@)", user];
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];
278
/* now check whether the roles subset */
280
e = [userRoles objectEnumerator];
281
while ((role = [e nextObject])) {
282
if ([rolesHavingPermission containsObject:role]) {
288
/* if no role was found, check whether the user is the owner */
291
if ([rolesHavingPermission containsObject:SoRole_Owner]) {
292
if ([self isUser:user ownerOfObject:_object inContext:_ctx]) {
294
[self debugWithFormat:@" user is owner of object."];
296
else if ([_object ownerInContext:_ctx] == nil) {
298
[self debugWithFormat:@" object is not owned, grant access."];
302
[self debugWithFormat:
303
@" user is not the owner of object (owner=%@).",
304
[_object ownerInContext:_ctx]];
309
/* check whether a role was finally found */
312
[self debugWithFormat:@" found no matching role."];
314
if ([[user login] isSoAnonymousUserLogin]) {
315
[self debugWithFormat:@"still anonymous, requesting login ..."];
316
return [SoAuthRequiredException securityExceptionOnObject:_object
318
[self authenticatorInContext:_ctx
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.
328
[self debugWithFormat:@"valid user, denying access ..."];
329
return [self isPrivateExceptionForObject:_object];
333
[self debugWithFormat:@" found a valid role: '%@'.", role];
336
[self debugWithFormat:@" successfully validated permission '%@'.", _perm];
337
[validatedPerms setObject:[NSNull null] forKey:_perm];
341
- (NSException *)validateObject:(id)_object inContext:(id)_ctx {
342
/* This methods check how the object itself is protected. */
343
NSMutableArray *validatedObjects;
344
SoClassSecurityInfo *sinfo;
348
if (_object == nil) return nil;
350
/* some objects are always public */
351
if ([_object isPublicInContext:_ctx])
354
/* check the cache */
355
if ((validatedObjects = [(WOContext *)_ctx objectPermissionCache])) {
356
if ([validatedObjects indexOfObjectIdenticalTo:_object] != NSNotFound)
361
validatedObjects = [[NSMutableArray alloc] init];
362
[(WOContext *)_ctx setObjectPermissionCache:validatedObjects];
363
[validatedObjects autorelease];
366
[self debugWithFormat:@"validate object: %@", _object];
368
/* find the security info which has object protections */
369
sinfo = [self lookupInfoOfClass:[_object soClass]
370
condition:@selector(hasObjectProtections)
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];
380
if ([sinfo isObjectPublic]) {
381
/* object is public ... */
382
[self debugWithFormat:@" object is public."];
383
[validatedObjects addObject:_object];
387
if ([sinfo isObjectPrivate]) {
388
/* object is private ... */
389
[self debugWithFormat:@" object is private."];
390
return [self isPrivateExceptionForObject:_object];
393
perm = [sinfo permissionRequiredForObject];
394
if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
397
[self debugWithFormat:@" successfully validated object (perm=%@).", perm];
398
[validatedObjects addObject:_object];
402
- (NSException *)validateName:(NSString *)_key
406
/* note: this does not check object-value restrictions */
407
SoClassSecurityInfo *sinfo;
411
/* step a: find out permission required for object */
413
if ((e = [self validateObject:_object inContext:_ctx])) {
414
[self debugWithFormat:@" object did not validate (tried lookup on %@).",
419
/* step b: find out permission required for key */
421
[self debugWithFormat:@"validate key %@ of object: %@", _key, _object];
423
/* find the security info which has protections for the key */
424
sinfo = [self lookupInfoOfClass:[_object soClass]
425
condition:@selector(hasProtectionsForKey:)
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];
433
sinfo = [self lookupInfoOfClass:[_object soClass]
434
condition:@selector(hasDefaultAccessDeclaration)
437
// TODO: search superclasses for one with declared default-access
438
if ([[sinfo defaultAccess] isEqualToString:@"allow"]) {
439
[self debugWithFormat:@" default is allow ..."];
442
return [self isPrivateKeyException:_key ofObject:_object];
445
if ([sinfo isKeyPublic:_key])
448
if ([sinfo isKeyPrivate:_key])
449
/* key is private ... */
450
return [self isPrivateKeyException:_key ofObject:_object];
452
perm = [sinfo permissionRequiredForKey:_key];
453
if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
456
[self debugWithFormat:@" successfully validated key (%@).", _key];
460
- (NSException *)validateValue:(id)_value
461
forName:(NSString *)_key
465
/* this additionally checks object restrictions of the value */
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];
475
return [self validateName:_key ofObject:_object inContext:_ctx];
478
@end /* SoSecurityManager */
480
@implementation SoSecurityManager(Logging)
481
// Note: this is a category, so that its more difficult to override (of course
482
// still not impossible ...
484
- (NSString *)loggingPrefix {
485
return @"[so-security]";
487
- (BOOL)isDebuggingEnabled {
488
return debugOn ? YES : NO;
491
@end /* SoSecurityManager(Logging) */
496
@implementation NSObject(Pub)
497
- (BOOL)isPublicInContext:(id)_ctx { return NO; }
500
@implementation NSArray(Pub)
501
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
504
@implementation NSString(Pub)
505
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
508
@implementation NSDictionary(Pub)
509
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
512
@implementation NSException(Pub)
513
- (BOOL)isPublicInContext:(id)_ctx { return YES; }
516
@implementation NSString(SpecialPermissionChecks)
518
- (BOOL)isSoPublicPermission {
519
return [@"<public>" isEqualToString:self];
521
- (BOOL)isSoAnonymousUserLogin {
522
return [@"anonymous" isEqualToString:self];
525
@end /* NSString(SpecialPermissionChecks) */