[TransWarp] Building PEAK apps with Zope X3

Phillip J. Eby pje at telecommunity.com
Fri Apr 4 09:27:23 EST 2003


At 09:50 PM 4/3/03 -0500, Phillip J. Eby wrote:

>Security
>--------
>
>zope.app.security is way overkill, but zope.security is actually pretty 
>cool.  We'll want to replace its "management" infrastructure (which, like 
>ZODB transactions, depend on per-thread singletons) but its base machinery 
>of proxies, checkers, and permissions IDs looks pretty solid.  Alas, we 
>will probably have to replace the entire 'zope.security.checker' module in 
>order to replace the "management" infrastructure, unless I come up with a 
>clever yet acceptable patch...  Too bad we can't use module inheritance on 
>this.  :)
>
>'zope.security.management' deals with the special 'securityManager' and 
>'securityPolicy' objects, and the mechanisms for replacing them.  These 
>objects are per-thread and per-interpreter, respectively, but there's 
>little reason for them to be.  In PEAK architecture, these should be 
>contextual utility components, like anything else.  Instead of accessing 
>these as singletons, the checker objects should be components, carrying 
>their manager and policy in context as normal for a PEAK component.

On further consideration, this doesn't look like as good of an idea as I 
thought.  It appears that if the security manager is a singleton, business 
object classes can each supply a single checker instance suitable for 
sharing by all instances of that class.  If we treat the security manager 
(or other thing used by the checker to verify a permission) as cached in 
the checker, we add an extra object creation overhead whenever an object is 
wrapped in a proxy (which is whenever it's accessed!).

So I think we'll keep 'zope.security.checker' and all its useful default 
checkers for built-in and related types.  It relies on 'datetime' (to 
declare checkers for those types), but that's probably okay since we will 
want 'datetime' for PEAK anyway.

The quickest way to install our own policies, is to just supply a "security 
policy" object, and keep all of the other basic 'zope.security' mechanisms 
in place.  Unfortunately, this means several levels of Python function 
calls between each attribute access/operation (even on basic types like 
integers!), and our policy object.  We could cut out one or two levels, 
however, by implementing our own "security manager" class as well, and 
monkeypatching it in.  We could also possibly contribute some speedup 
patches to the existing security manager.  However, for our initial use we 
could get by with just setting the policy (and of course creating 
authentication services and anything else needed for the policy itself to 
work.)

Another issue that's come up, is that code on the untrusted side of a 
proxy, is going to supply proxies to anything it uses, which means that 
peak.model objects may have their attributes set to proxied values.  DM 
methods that accept objects may need to unproxy them.  Mainly, though, this 
will affect _new and _save methods, if they need to introspect objects 
contained in the target object's state in some way in order to save 
them.  (The object being saved or loaded will be introspectable, but the 
contents of its state may contain proxies.)  Presumably, ZODB just 
unproxies everything.

The good thing about this is that once again it exposes code that 
inadvertently grants greater access to an object than has been officially 
granted to the current principal.  Although as I consider Zope proxies I 
initially react with dismay at the loss of Python's "open access", I find 
as I reflect that they actually ensure that security holes, like errors, 
"never pass silently, unless explicitly silenced."  And that's a good thing.

One ugly bit...  bidirectional associations in 'peak.model' break 
encapsulation on the supplied object.  So if 
'someObject.setFoo(proxiedObject)' requires that 'proxiedObject.bar' 
reference 'someObject', then 'setFoo()' is going to want to call 
proxiedObject.__class__.bar._link().  We don't want to allow untrusted code 
access to that, because it breaks consistency if you can call that 
directly.  But, if the _notifyLink method unwraps the other object, then 
that is code granting a higher privilege level than the caller had.  That 
is, if you have access to one end of an association, you can force a 
security breakthrough of sorts at the other end.  Of course, the other end 
has to pass through the 'normalize()' method first to verify that it's of 
an acceptable type...  so presumably that could be the correct place to 
remove a proxy as well.  (But then that loses the "security errors never 
pass silently" bit.)

So we'll need to make a suitable tradeoff and decide whether to unwrap only 
for what needs to break encapsulation (better for security, and for 
performance when proxies aren't present) or to unwrap on value 
normalization (better for convenience, and for performance when proxies are 
present).

Another issue is that normalization doesn't usually *do* anything right 
now.  It doesn't do any isinstance() or interface checking, for example, 
unless specifically overridden to do so.  So these seem to be our choices:

1. Don't do anything to unwrap things by default, and require 
normalize()/mdl_normalize() to unwrap if needed, only after validating that 
it's *really* the right type.  Plus: high security.  Minus: tedious, 
repetitive, and error-prone to code.

2. Expose the other-end _link operation somehow and thus make it subject to 
security checks.  Require that the principal have access to it, and somehow 
restrict that access so it only occurs by way of the first end's 
_notifyLink method.  Plus: correct security, without extra coding at the 
app level.  Minus: I haven't a clue how to do *either* part of this...

I'll have to give this some more thought, especially since I'd like to 
avoid bogging peak.model down with lots of extra security-specific code.  :(




More information about the PEAK mailing list