[ZDP] BackTalk to Document Zope Developer's Guide (2.4
edition)/Security
webmaster at zope.org
webmaster at zope.org
Mon Mar 8 04:18:50 EST 2004
A comment to the paragraph below was recently added via http://zope.org/Documentation/Books/ZDG/current/Security.stx#3-78
---------------
In other words, security declarations that you make using
'ClassSecurityInfo' objects effect instances of the class upon
which you make the declaration. You only need to make security
declarations for the methods and subobjects that your class
actually *defines*. If your class inherits from other classes,
the methods of the base classes are protected by the security
declarations made in the base classes themselves. The only time
you would need to make a security declaration about an object
defined by a base class is if you needed to *redefine* the
security information in a base class for instances of your own
class. An example below redefines a security assertion in a
subclass::
from AccessControl import ClassSecurityInfo
import Globals
class MailboxBase(ObjectManager):
"""A mailbox base class."""
# Create a SecurityInfo for this class
security = ClassSecurityInfo()
security.declareProtected('View Mailbox', 'listMessages')
def listMessages(self):
"""Return a sequence of message objects."""
return self._messages[:]
security.setPermissionDefault('View Mailbox', ('Manager', 'Mailbox Owner'))
Globals.InitializeClass(MailboxBase)
class MyMailbox(MailboxBase):
"""A mailbox subclass, where we want the security for
listMessages to be public instead of protected (as
defined in the base class)."""
# Create a SecurityInfo for this class
security = ClassSecurityInfo()
security.declarePublic('listMessages')
Globals.InitializeClass(MyMailbox)
% Anonymous User - June 25, 2002 6:13 am:
"If your class inherits from other classes, the methods of the base classes are protected by the security
declarations made in the base classes themselves. "
Maybe this is true for non-static methods, however I have contradicting experience with static methods:
class base:
index_html = Globals.DTMLFile('dtml/index_html',globals())
security.declarePrivate('index_html')
class derived(base):
pass # index_html *is* accessible here ...
Regards
dvadasz _at_ amadeus _dot_ net
% rboylan - July 20, 2002 2:07 am:
There is a lot of ambiguity in this whole section. I have summarized
Chris McDonough's responses in [].
1. If a subclass redefines a base class method, does the subclass need
to do a security declaration on it? The document says "You only need
to make security declarations for methods .... your class actually
defines. If your class inherits from other classes, the methods of
the base classes are protected by the security declarations made in
the base classes." The first sentence seems to indicate a security
declaration is necessary (since you define the method); the second
sentence suggests its not. It depends partly on the meaning of
"define" and also "method" (that is, is redefinition considered
definition? does method refer to a name or to a specific classes
implementation of that name?). [Yes, you do need to redeclare security.]
2. Does a subclass need to have
security = ClassSecurityInfo()
in it if the base class does? Judging from the example, yes. [yes]
3. Under what circumstances is the declaration in 2 necessary? For
example, only if new method names are introduced and protected? Or
any reference to security in the subclass? It seems the latter, from
the example. [any time a class references the security object it should define
a new one with security = ClassSecurityInfo().]
4. Suppose we wanted to change the security of a base class method
without otherwise redefining it. What's necessary then?
[You need to have a security = ... statement and you need to do
an InitializeClass.]
5. Under what conditions is InitializeClass necessary for the subclass
when the base class has been through InitializeClass? (The guide only
addresses the case when the base class has not been so processed. It
also says the declarations "filter down", but the implication of this
for new method is unclear.)
[Anytime you have a security = you should do an InitializeClass.
You can never cause trouble by doing an InitializeClass]
This section has a lot of explicit discussion of odd cases (no
security in superclass, redefining permissions on existing methods
without changing them) and not enough about the normal cases (my
subclass extends some base class methods and defines some new ones).
[A later response indicated it might be possible to get away without some of these activities, but it is
definitely safe to do them.]
RossBoylan at stanfordalumni.org
% Anonymous User - July 20, 2002 2:44 pm:
Let's do some investigation. This is going to be sublimely painful, I'm sure. ;-)
Let's define a simple Product named SecurityTest.
Its __init__.py is this:
import SecurityTest
from Products.Transience import Transience
form = SecurityTest.constructSecurityTestForm
constructor = SecurityTest.constructSecurityTest
def initialize(context):
context.registerClass(
SecurityTest.SecurityTest,
constructors=(form, constructor),
)
We define a module named SecurityTest in the Product, which is initially just the following:
from Products.Transience.Transience import TransientObjectContainer
from Globals import HTMLFile, InitializeClass
from AccessControl import ClassSecurityInfo
class SecurityTest(TransientObjectContainer):
meta_type = 'Security Test'
constructSecurityTestForm = HTMLFile(
'dtml/addSecurityTest', globals())
def constructSecurityTest(self, id, title='', timeout_mins=20,
addNotification=None, delNotification=None, limit=0, REQUEST=None):
""" """
ob = SecurityTest(id, title, timeout_mins,
addNotification, delNotification, limit=limit)
self._setObject(id, ob)
if REQUEST is not None:
return self.manage_main(self, REQUEST, update_menu=1)
The HTMLFile instance is just a copy of the constructTransientObjectContainer form in the Transience product
modified with the right target to create a SecurityTest instance. Note that we don't run the SecurityTest
class through InitializeClass, nor do we allow it to make any of its own security declarations.
Then we fire up Zope, log in as the admin user and call the URL
http://localhost:8080/security_test/foo/getSubobjectLimit .
Note that we didn't declare any security assertions on the SecurityTest class and we didn't run the
SecurityTest class through InitializeClass, and we can still view the getSubobjectLimit method, which is
defined in the base class and protected by the 'View management screens' permission in the base class. So we
know we don't have to run InitializeClass on a subclass of a security-aware class which doesn't override any
of its superclass' methods.
Let's go override the getObjectLimit method in the SecurityTest subclass.
class SecurityTest(TransientObjectContainer):
meta_type = 'Security Test'
def getSubobjectLimit(self):
""" """
return 100
When calling the same URL, we find that we can still call the getSubobjectLimit method while logged in as the
admin user, even though we didn't give it any security declarations of its own (its security declaration was
inherited from the base class). So we know we don't need to declare security assertions on methods of classes
which inherit from base classes which have security assertions unless we want to change those assertions.
So now let's go modify the base class, declaring the getSubobjectLimit method private by changing the class:
#security.declareProtected(MGMT_SCREEN_PERM, 'getSubobjectLimit')
security.declarePrivate('getSubobjectLimit')
def getSubobjectLimit(self):
""" """
return self._limit
In either the case where we override the security declaration in the SecurityTest subclass or we remove the
method from the subclass, we are not able to access the method any longer via its URL. So we are really,
definitely, positively sure that we inherit security declarations from our base class.
Then let's make our own security declaration on the getSubobjectLimit method in the subclass, but we won't
run the class through InitializeClass:
class SecurityTest(TransientObjectContainer):
meta_type = 'Security Test'
security = ClassSecurityInfo()
security.declarePublic('getSubobjectLimit')
def getSubobjectLimit(self):
""" """
return 100
In the base class, the getSubobjectLimit method is still declared private. But we find that even though we
didn't run the SecurityTest class through InitializeClass, the getSubobjectLimit method is now accessible! This
leads us to believe that we needn't run InitializeClass on our subclass if one of its base classes has
already been run through InitializeClass. How this security declaration gets applied to our class, I don't
know. ;-) That's the job of InitializeClass, but it's apparently unnecessary in subclasses of classes that
are already run through InitializeClass.
Let's run the SecurityTest class through InitializeClass now:
class SecurityTest(TransientObjectContainer):
meta_type = 'Security Test'
security = ClassSecurityInfo()
security.declarePublic('getSubobectLimit')
def getSubobjectLimit(self):
""" """
return 100
InitializeClass(SecurityTest)
This works, of course, and the method is still public.
Now, lets go pare out our method declaration and our security assertions from our subclass, but we'll still
run the class through InitializeClass:
class SecurityTest(TransientObjectContainer):
meta_type = 'Security Test'
# security = ClassSecurityInfo()
# security.declarePublic('getSubobjectLimit')
# def getSubobjectLimit(self):
# """ """
# return 100
InitializeClass(SecurityTest)
We also go back and change our TransientObjectContainer base class' security declarations back to standard:
security.declareProtected(MGMT_SCREEN_PERM, 'getSubobjectLimit')
#security.declarePrivate('getSubobjectLimit')
def getSubobjectLimit(self):
""" """
return self._limit
We find that we can still access getSubobjectLimit, which is what I would expect.
So, the (somewhat suprising) morals of the story are:
- you needn't use InitializeClass on classes which inherit
from a base class which has security assertions
and has itself been run through InitializeClass if
a) you don't add any methods to the subclass and b)
you're willing to accept the base class' security
assertions. Not suprising.
- You needn't declare security assertions on overriding methods
of subclasses of security-aware base classes unless you want
to change those assertions. Not suprising.
- It's always safe to run a class through InitializeClass even
if it does not have security declarations of its own. Not
suprising.
- If you declare differing security assertions in your subclass,
you do not need to run the subclass through InitializeClass
for those security assertions to have an effect. Why this
is the case is still somewhat a mystery. Surprising.
I'm sort of stumped as to how the subclass' assertions are applied in the absence of InitializeClass! This is
not what I expected, I would have thought that differing assertions would only be applied if InitializeClass
was called on the subclass. There's some magic going on here that I don't understand.
But in a nutshell, if you don't want to think about any of this (and god knows I don't):
- declare security assertions for each method that you define,
even if there is an existing security declaration for
the method in a superclass. It's always clear what the
intention of your code is then, and you won't piss off
any of your coworkers. ;-)
- always run your classes through InitializeClass
- C
% Anonymous User - July 20, 2002 8:43 pm:
Florent Guillaume clears things up:
> The magic is that Persistent has a __class_init__ that calls
> InitializeClass for you. (This attribute is actually set by
> App.PersistentExtra, called from Globals.)
This is why it's not necessary to run an inherited subclass through InitializeClass (if it inherits from
Persistent).
% mcdonc - Mar. 8, 2004 4:18 am:
From Collector issue 270:
I dont seem to be able to inherit security declarations for names beginning with "manage", see
the test product attached. default__class_init (in App/class_init.py), ca. line 119 seems to hint
that names beginning with "manage" are treated differentlyIf this is intended behaviour I think it
should be documented in the DevGuide, under "Inheritance And Class Security Declarations" (is it
documented somewhere?)
It is now. ;-) If there is not a security assertion associated directly in the class in which they are
defined, methods that begin with "manage_" will automatically be protected with assertions that allow only
users with the 'Manager' role to call them.
More information about the ZDP
mailing list