[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