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%p, 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%p, 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 // [self logWithFormat:@"CHECK CLASS: %@", soClass];
130 if ((sinfo = [soClass soClassSecurityInfo]) == nil) continue;
131 if ((m = [sinfo methodForSelector:_sel])) {
135 ? ((BOOL (*)(id, SEL, id))m)(sinfo, _sel, _object)
136 : ((BOOL (*)(id, SEL))m)(sinfo, _sel);
137 if (ok) return sinfo;
145 - (id)authenticatorInContext:(id)_ctx object:(id)_object {
148 if ((authenticator = [_object authenticatorInContext:_ctx]) == nil)
149 authenticator = [[WOApplication application] authenticatorInContext:_ctx];
150 return authenticator;
152 - (id<SoUser>)userInContext:(id)_ctx object:(id)_object {
153 id user, authenticator;
155 if ((user = [(WOContext *)_ctx activeUser]) != nil)
156 return [user isNotNull] ? user : nil;
158 authenticator = [self authenticatorInContext:_ctx object:_object];
160 if ((user = [authenticator userInContext:_ctx]) != nil)
161 [(WOContext *)_ctx setActiveUser:user];
163 return [user isNotNull] ? user : nil;
166 - (BOOL)isUser:(id<SoUser>)_user ownerOfObject:(id)_obj inContext:(id)_ctx {
167 NSString *objectOwner;
169 if ((objectOwner = [_obj ownerInContext:_ctx]) == nil)
172 if ([[_user login] isEqualToString:objectOwner])
178 - (NSException *)validatePermission:(NSString *)_perm
182 NSMutableDictionary *validatedPerms;
183 NSArray *rolesHavingPermission;
184 SoClassSecurityInfo *sinfo;
191 return [self missingPermissionException:_perm forObject:_object];
194 validatedPerms = nil;
196 // TODO: Bug !! The cache must go on Permission+ObjectID since the
197 // permission can be set without the ID !
199 /* check the cache */
200 if ((validatedPerms = [_ctx objectForKey:SoPermCache])) {
203 if ((o = [validatedPerms objectForKey:_perm])) {
205 [self debugWithFormat:@"permission '%@' cached as valid ...", _perm];
215 validatedPerms = [[NSMutableDictionary alloc] init];
216 [_ctx setObject:validatedPerms forKey:SoPermCache];
217 [validatedPerms autorelease];
222 [self debugWithFormat:@"validate permission '%@' on object: %@",
226 if ([_perm isSoPublicPermission])
227 /* the object is public */
230 /* determine the possible roles for the permission */
232 // TODO: check object for policy (currently only default roles are checked)
234 sinfo = [self lookupInfoOfClass:[_object soClass]
235 condition:@selector(hasDefaultRoleForPermission:)
239 sinfo = [[_object soClass] soClassSecurityInfo];
241 rolesHavingPermission = [sinfo defaultRolesForPermission:_perm];
243 [self debugWithFormat:@" possible roles for permission '%@': %@",
244 _perm, [rolesHavingPermission componentsJoinedByString:@", "]];
247 if ([rolesHavingPermission containsObject:SoRole_Anonymous]) {
249 [self debugWithFormat:@" allowed because of anonymous roles."];
252 if ([rolesHavingPermission count] == 0) {
254 [self debugWithFormat:@" allowed because no roles are required."];
258 /* now retrieve the user that is logged in */
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
265 [self authenticatorInContext:_ctx
270 [self debugWithFormat:@" got user: %@)", user];
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];
280 /* now check whether the roles subset */
282 e = [userRoles objectEnumerator];
283 while ((role = [e nextObject])) {
284 if ([rolesHavingPermission containsObject:role]) {
290 /* if no role was found, check whether the user is the owner */
293 if ([rolesHavingPermission containsObject:SoRole_Owner]) {
294 if ([self isUser:user ownerOfObject:_object inContext:_ctx]) {
296 [self debugWithFormat:@" user is owner of object."];
298 else if ([_object ownerInContext:_ctx] == nil) {
300 [self debugWithFormat:@" object is not owned, grant access."];
304 [self debugWithFormat:
305 @" user is not the owner of object (owner=%@).",
306 [_object ownerInContext:_ctx]];
311 /* check whether a role was finally found */
314 [self debugWithFormat:@" found no matching role."];
316 if ([[user login] isSoAnonymousUserLogin]) {
317 [self debugWithFormat:@"still anonymous, requesting login ..."];
318 return [SoAuthRequiredException securityExceptionOnObject:_object
320 [self authenticatorInContext:_ctx
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.
330 [self debugWithFormat:@"valid user, denying access ..."];
331 return [self isPrivateExceptionForObject:_object];
335 [self debugWithFormat:@" found a valid role: '%@'.", role];
338 [self debugWithFormat:@" successfully validated permission '%@'.", _perm];
339 [validatedPerms setObject:[NSNull null] forKey:_perm];
343 - (NSException *)validateObject:(id)_object inContext:(id)_ctx {
344 /* This methods check how the object itself is protected. */
345 NSMutableArray *validatedObjects;
346 SoClassSecurityInfo *sinfo;
350 if (_object == nil) return nil;
352 /* some objects are always public */
353 if ([_object isPublicInContext:_ctx])
356 /* check the cache */
357 if ((validatedObjects = [(WOContext *)_ctx objectPermissionCache])) {
358 if ([validatedObjects indexOfObjectIdenticalTo:_object] != NSNotFound)
363 validatedObjects = [[NSMutableArray alloc] init];
364 [(WOContext *)_ctx setObjectPermissionCache:validatedObjects];
365 [validatedObjects autorelease];
368 [self debugWithFormat:@"validate object: %@", _object];
370 /* find the security info which has object protections */
371 sinfo = [self lookupInfoOfClass:[_object soClass]
372 condition:@selector(hasObjectProtections)
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];
382 if ([sinfo isObjectPublic]) {
383 /* object is public ... */
384 [self debugWithFormat:@" object is public."];
385 [validatedObjects addObject:_object];
389 if ([sinfo isObjectPrivate]) {
390 /* object is private ... */
391 [self debugWithFormat:@" object is private."];
392 return [self isPrivateExceptionForObject:_object];
395 perm = [sinfo permissionRequiredForObject];
396 if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
399 [self debugWithFormat:@" successfully validated object (perm=%@).", perm];
400 [validatedObjects addObject:_object];
404 - (NSException *)validateName:(NSString *)_key
408 /* note: this does not check object-value restrictions */
409 SoClassSecurityInfo *sinfo;
413 /* step a: find out permission required for object */
415 if ((e = [self validateObject:_object inContext:_ctx])) {
416 [self debugWithFormat:@" object did not validate (tried lookup on %@).",
421 /* step b: find out permission required for key */
423 [self debugWithFormat:@"validate key %@ of object: %@", _key, _object];
425 /* find the security info which has protections for the key */
426 sinfo = [self lookupInfoOfClass:[_object soClass]
427 condition:@selector(hasProtectionsForKey:)
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];
435 sinfo = [self lookupInfoOfClass:[_object soClass]
436 condition:@selector(hasDefaultAccessDeclaration)
439 // TODO: search superclasses for one with declared default-access
440 if ([[sinfo defaultAccess] isEqualToString:@"allow"]) {
441 [self debugWithFormat:@" default is allow ..."];
444 return [self isPrivateKeyException:_key ofObject:_object];
447 if ([sinfo isKeyPublic:_key])
450 if ([sinfo isKeyPrivate:_key])
451 /* key is private ... */
452 return [self isPrivateKeyException:_key ofObject:_object];
454 perm = [sinfo permissionRequiredForKey:_key];
455 if ((e = [self validatePermission:perm onObject:_object inContext:_ctx]))
458 [self debugWithFormat:@" successfully validated key (%@).", _key];
462 - (NSException *)validateValue:(id)_value
463 forName:(NSString *)_key
467 /* this additionally checks object restrictions of the value */
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];
477 return [self validateName:_key ofObject:_object inContext:_ctx];
480 @end /* SoSecurityManager */
482 @implementation SoSecurityManager(Logging)
483 // Note: this is a category, so that its more difficult to override (of course
484 // still not impossible ...
486 - (NSString *)loggingPrefix {
487 return @"[so-security]";
489 - (BOOL)isDebuggingEnabled {
490 return debugOn ? YES : NO;
493 @end /* SoSecurityManager(Logging) */
498 @implementation NSObject(Pub)
499 - (BOOL)isPublicInContext:(id)_ctx { return NO; }
502 @implementation NSArray(Pub)
503 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
506 @implementation NSString(Pub)
507 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
510 @implementation NSDictionary(Pub)
511 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
514 @implementation NSException(Pub)
515 - (BOOL)isPublicInContext:(id)_ctx { return YES; }
518 @implementation NSString(SpecialPermissionChecks)
520 - (BOOL)isSoPublicPermission {
521 return [@"<public>" isEqualToString:self];
523 - (BOOL)isSoAnonymousUserLogin {
524 return [@"anonymous" isEqualToString:self];
527 @end /* NSString(SpecialPermissionChecks) */