Re: [Zope-dev] Improvements for Zope2's security

2006-09-18 Thread Chris McDonough
I think it's great that you did this... nice job!  I have some  
specific disagreements (while I think it's a reasonable constraint,  
and I think something should enforce it, I don't believe it's the job  
of something that we call a *security policy* to enforce  whether a  
method is called, e.g. via POST rather than GET), I think you did a  
wonderful job analyzing this..  +1 on the developer-helper tools  
portion of this that finds declarations that have no corresponding  
method.


Thanks!

- C

On Sep 18, 2006, at 11:20 AM, Christian Heimes wrote:


Hey guys!

In the past few months I fiddled around with Zope2's security and  
access
control code. I analysied my own code and code from other  
developers to

search for common errors. Also I tried to think of ways to make the
security system easier and more verbose on coding errors

I have not yet implemented all my ideas but Zope 2.10 is on the door
steps. Here is my first set of improvements.

Issue 1
===

Zope's security declarations have to be called with a method *name* AS
STRING. Developers are human beeings and human beeings tend to make
small errors like typos. Or they forget to change the security
declaration when they rename a method. Zope doesn't raise an error  
when

a developer adds a security declaration for a non existing method.

Have a look at the following example. It contains a tiny but  
devastating

typo::

security.declarePrivate('chooseProtocol')
def chooseProtocols(self, request):
...

These kinds or errors are extremly hard to find and may lead to big
security holes. By the way this example was taken from a well known  
and

well tested Zope addon!

Solution


The solution was very easy to implement. I created a small helper
function checkClassHasMethod() and called it in the apply() method of
AccessControl.SecurityInfo.ClassSecurityInfo. The apply() method is
called at startup. The code doesn't slow down requests.

Issue 2
===

Another way to introduce security breaches is to forget the
InitializeClass() call. The call is responsable for applying the
informations from a ClassSecurityInfo. Without the call all security
settings made through security.declare* are useless.

The good news is: Even if a developer forgets to call the method
explictly in his code the function is called implictly.

The implicit call was hard to find for me at first. I struggled  
with the

code in OFS.Image because there is an import of InitializeClass but it
is never called. Never the less the security informations are somehow
merged automagically. After some digging I found the code that is
responsible for the magic. It's ExtensionClass' __class_init__  
magic and

OFS.ObjectManager.ObjectManager.__class_init__

Now the bad news: The magic doesn't work under some conditions. For
example if you class doesn't subclass ObjectManager or if you monkey
patch a class with security info object you have to call  
InitializeClass

explictly.

Solution


Not yet finished. I've created a function checkObjectHasSecurityInfo()
and added a call to ZPublisher.BaseRequest.DefaultPublishTraverse  
but it

is untested.

Issue 3
===

Developers are lazy and they like to make typos. No one likes to type
security.declarePrivate('chooseProtocol') so we are using copy & paste
which may cause even more typos. Wouldn't it be cool to get rid of
security. and typing the name of the method twice? Let's use  
decorators!

Here is the doc test example from my patch:

Solution


Security decorators are an alternative syntax to define security
declarations on classes.


from ExtensionClass import Base
from AccessControl import ClassSecurityInfo
from AccessControl.decorator import declarePublic
from AccessControl.decorator import declarePrivate
from AccessControl.decorator import declareProtected
from AccessControl.Permissions import view as View
from Globals import InitializeClass



class DecoratorExample(Base):

... '''decorator example'''
...
... security = ClassSecurityInfo()
...
... @declarePublic
... def publicMethod(self):
... "public method"
...
... @declarePrivate
... def privateMethod(self):
... "private method"
...
... @declareProtected(View)
... def protectedByView(self):
... "method protected by View"
...

InitializeClass(DecoratorExample)



With the new syntax you have to type only 15 letters instead of 41!

Issue 4
===

Some methods shouldn't be callable from certain types of request
methods. For example there is no need that the webdav DELETE and PROP*
methods should be callable from an ordinary HTTP GET request. I don't
want to go into details. Some people know why :)

Solution


Only a small subset is implemented. There are two methods in my  
patch to

either whitelist or blacklist a request method. An older version of my
patch contained even code to distinguish between request types (ftp,
http, ftp, xml-rpc) but Jim told me in a private mail

Re: [Zope-dev] Improvements for Zope2's security

2006-09-18 Thread Lennart Regebro

You have many good points in your list of troubles. Many of them are
resolved by using security declarations through ZCML instead. It would
be interesting to here your views on this.

//Lennart
___
Zope-Dev maillist  -  Zope-Dev@zope.org
http://mail.zope.org/mailman/listinfo/zope-dev
**  No cross posts or HTML encoding!  **
(Related lists - 
http://mail.zope.org/mailman/listinfo/zope-announce

http://mail.zope.org/mailman/listinfo/zope )


[Zope-dev] Improvements for Zope2's security

2006-09-18 Thread Christian Heimes
Hey guys!

In the past few months I fiddled around with Zope2's security and access
control code. I analysied my own code and code from other developers to
search for common errors. Also I tried to think of ways to make the
security system easier and more verbose on coding errors

I have not yet implemented all my ideas but Zope 2.10 is on the door
steps. Here is my first set of improvements.

Issue 1
===

Zope's security declarations have to be called with a method *name* AS
STRING. Developers are human beeings and human beeings tend to make
small errors like typos. Or they forget to change the security
declaration when they rename a method. Zope doesn't raise an error when
a developer adds a security declaration for a non existing method.

Have a look at the following example. It contains a tiny but devastating
typo::

security.declarePrivate('chooseProtocol')
def chooseProtocols(self, request):
...

These kinds or errors are extremly hard to find and may lead to big
security holes. By the way this example was taken from a well known and
well tested Zope addon!

Solution


The solution was very easy to implement. I created a small helper
function checkClassHasMethod() and called it in the apply() method of
AccessControl.SecurityInfo.ClassSecurityInfo. The apply() method is
called at startup. The code doesn't slow down requests.

Issue 2
===

Another way to introduce security breaches is to forget the
InitializeClass() call. The call is responsable for applying the
informations from a ClassSecurityInfo. Without the call all security
settings made through security.declare* are useless.

The good news is: Even if a developer forgets to call the method
explictly in his code the function is called implictly.

The implicit call was hard to find for me at first. I struggled with the
code in OFS.Image because there is an import of InitializeClass but it
is never called. Never the less the security informations are somehow
merged automagically. After some digging I found the code that is
responsible for the magic. It's ExtensionClass' __class_init__ magic and
OFS.ObjectManager.ObjectManager.__class_init__

Now the bad news: The magic doesn't work under some conditions. For
example if you class doesn't subclass ObjectManager or if you monkey
patch a class with security info object you have to call InitializeClass
explictly.

Solution


Not yet finished. I've created a function checkObjectHasSecurityInfo()
and added a call to ZPublisher.BaseRequest.DefaultPublishTraverse but it
is untested.

Issue 3
===

Developers are lazy and they like to make typos. No one likes to type
security.declarePrivate('chooseProtocol') so we are using copy & paste
which may cause even more typos. Wouldn't it be cool to get rid of
security. and typing the name of the method twice? Let's use decorators!
Here is the doc test example from my patch:

Solution


Security decorators are an alternative syntax to define security
declarations on classes.

>>> from ExtensionClass import Base
>>> from AccessControl import ClassSecurityInfo
>>> from AccessControl.decorator import declarePublic
>>> from AccessControl.decorator import declarePrivate
>>> from AccessControl.decorator import declareProtected
>>> from AccessControl.Permissions import view as View
>>> from Globals import InitializeClass

>>> class DecoratorExample(Base):
... '''decorator example'''
...
... security = ClassSecurityInfo()
...
... @declarePublic
... def publicMethod(self):
... "public method"
...
... @declarePrivate
... def privateMethod(self):
... "private method"
...
... @declareProtected(View)
... def protectedByView(self):
... "method protected by View"
...
>>> InitializeClass(DecoratorExample)


With the new syntax you have to type only 15 letters instead of 41!

Issue 4
===

Some methods shouldn't be callable from certain types of request
methods. For example there is no need that the webdav DELETE and PROP*
methods should be callable from an ordinary HTTP GET request. I don't
want to go into details. Some people know why :)

Solution


Only a small subset is implemented. There are two methods in my patch to
either whitelist or blacklist a request method. An older version of my
patch contained even code to distinguish between request types (ftp,
http, ftp, xml-rpc) but Jim told me in a private mail it's kind of YAGNI.

At the moment blacklistRequestMethod() and whitelistRequestMethod() have
to be called explictly inside a method. There is no way to protect
methods via security.blacklistRequestMethod() or @blacklistRequestMethod.

I have two ideas to implement such security declarations but both ways
are complicated and hard to implement. An ordinary decorator doesn't
work because it messes up with ZPublisher.mapply.

The following code does NOT work with POST requests because mapply is
using introspection to get the names and default values of a method