[Zope-dev] Ambiguities in "Inheritance and Class Security Declaration"

Chris McDonough chrism@zope.com
Sat, 20 Jul 2002 14:41:05 -0400


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

----- Original Message -----
From: "Chris McDonough" <chrism@zope.com>
To: "Ross Boylan" <RossBoylan@stanfordalumni.org>
Cc: <zope-dev@zope.org>
Sent: Friday, July 19, 2002 9:20 PM
Subject: Re: [Zope-dev] Ambiguities in "Inheritance and Class Security
Declaration"


> Ross Boylan wrote:
> > After reading this section of the development guide, I have a lot
of
> > questions (pp 75-76 of the guide).
> >
> > 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?).
>
> You should have security declarations for each method you define,
even
> if they are defined in the base class.
>
>
> >
> > 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.
>
> Define security assertions for all the methods you define.
>
> >
> > 4. Suppose we wanted to change the security of a base class method
> > without otherwise redefining it.  What's necessary then?
>
> Define a security declaration for a method without actually defining
the
> method.  Your security declaration will override those of the base
class.
>
> >
> > 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.)
>
> When you define new methods or when you want to override security
> declarations in the base class.  It never hurts to run a class
through
> 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).
> >
> > Also, the second paragraph uses "superclass" where I hope it means
> > subclass.
>
> These comments would be more helpful if they were made using the
comment
> system that is in the book itself.  I will unfortunately forget
about
> this maillist message in about 10 minutes, but if they were made
inline,
> when I went to go edit the book, there they'd be. ;-)
>
> > Although I would appreciate and responses from the list, I would
also
> > like to send these comments to the documents authors.
Unfortunately,
> > I see no authorship or contact information in the document.  Can
> > anyone suggest some?
>
> I wrote most of it.  You can ask, I may be able to answer. ;-)
>
> --
> Chris McDonough                    Zope Corporation
> http://www.zope.org             http://www.zope.com
> "Killing hundreds of birds with thousands of stones"
>
>
>
> _______________________________________________
> Zope-Dev maillist  -  Zope-Dev@zope.org
> http://lists.zope.org/mailman/listinfo/zope-dev
> **  No cross posts or HTML encoding!  **
> (Related lists -
>  http://lists.zope.org/mailman/listinfo/zope-announce
>  http://lists.zope.org/mailman/listinfo/zope )
>