[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