You are not logged in Log in Join
You are here: Home » Members » mcdonc » PDG » chap5zdg.stx » View Document

Log in
Name

Password

 

chap5zdg.stx

*Zope 2.3. Security*

Introduction

Zope's primary goal is to present a foundation for creating customized web applications. A typical web application needs to be securely managed. Different types of users need different kinds of access to the components that make up an application. To this end, Zope includes a comprehensive --and sometimes confusing-- set of security features. This chapter's goal is to shed a little light on Zope security in the context of Zope Product development. For a "user's-eye" view of Zope security, you may wish to refer to the Zope Book's security chapter at http://www.zope.org/Members/michel/ZB/Security.dtml . Before diving in to this chapter, you should have an understanding of how to build Zope Products (see the Products chapter of this document at XXX).

Security Architecture

The Zope security infrastructure is built around a security policy, which you can think of as the access control philosophy of Zope. This policy constrains access to objects via direct Web traversal or via restricted code.

Restricted code is generally any sort of logic that may be edited remotely (through the Web, FTP, via !WebDAV or by other means). DTML objects and !SQLMethods are examples of restricted code. Python Scripts or Perl Scripts that are editable through the Web constiute restricted code as well.

When restricted code runs, any access to objects integrated with Zope security is arbitrated by the security policy. For example if you write a bit of restricted code with a line that attempts to grab an object you don't have sufficient permission to use, the security infrastructure will raise an Unauthorized exception automatically. When an Unauthorized exception is raised within Zope, it is generally handled in a sane way by Zope, usually by prompting the user for login information. Using this functionality, it's possible to protect Zope objects through access control, only prompting the user for authentication when it is necessary to perform an action which requires privilege.

There are also types of unrestricted code in Zope, where the logic is not constrained by the security policy. Examples of unrestricted code are the methods of Python classes that implement the objects in Python-based add on components and External Method objects. These are allowed to run unrestricted since access to the file system is required to define such logic - and hopefully you're not letting untrusted people run around in your filesystem! We'll see later that while the security policy does not constrain what your unrestricted code does, it can and should be used to control the ability to call your unrestricted code from within a restricted-code environment.

In general, all Zope objects and methods have the potential to be addressable and callable via direct Web traversal. For example, a method implemented in Zope can ofen times be called by typing its URL into a browser. The same method's return value can generally be captured via an XML-RPC request. However, there is a subtle difference between objects and methods which are directly web-accessible and those which are not. For example, it is possible to write a method in Zope which allows itself to be called from other Zope code, but does not allow itself to be called directly via a web browser or via XML-RPC. This behavior is not technically a part of the Zope security policy, it's rather a policy enforced by Zope's web publishing implementation, but it's important enough to be mentioned within this section.

Using The Security Machinery

Concretely, security within Zope is defined by users, user folders, authentication credentials, roles, permissions, and ownership. These are the facilities that allow your Product and your Product's users to secure object instances which are created from your Product. In the case you're building a Product using ZClasses, these facilities protect your Product definitions themselves.

There are two ways to make use of Zope's security features: decaratively and programmatically. You won't see the terms "declarative" and "programmatic" inside the Zope management interface to describe security. For example, you won't see a tab that says "Declarative Security" on a Zope management screen, and you won't see a DTML Method with the words "insert programmatic security code here". The terms are purely conceptual.

Declarative Security

Zope Product developers may associate permissions with constructors, classes, methods and attributes of Product-defined objects. They may additionally specify that a permission be granted by default to a particular role.

Later, when the product has successfully been installed, system administrators can associate Product-defined permissions with site-defined roles by using the Zope web management interface. System administrators may then further associate these roles with users. Users manipulate objects which expose these methods and attributes. Here's a graphical representation of the relationship between users, roles, objects and permissions:

Figure X-X: Roles, Users and Permissions

This is what is known as Zope's declarative security model. All Zope objects may be protected from unauthorized access by providing permission settings to objects and by assigning roles to Zope users. For example, a developer might protect the execution of a Zope object named "MyObject" defined within a Product with the "View" permission. He furthermore may declare that by default "only users with the Manager role may view an object named MyObject or its subobjects."

It's important to understand when making use of declarative security that your Product generally should only need to define permissions and default roles. It should generally not be the case that your product should define site-specific roles or users. Specific roles and specific users are the domain of the system administrator who uses your Product, while permissions and default roles are the domain of you, the Product developer. The administrator utlimately should have the "last word" about which roles are assigned which permissions, thus protecting her site in a manner that fits her business goals. This may even include overriding the default roles specified within a Product, as they are simply a suggestion.

Using declarative security in Zope is roughly analogous to assigning permission bit settings and ownership information to files in a UNIX or Windows filesystem. Declarative security allows developers and administrators to secure Zope object independently of statements made in application code.

Programmatic Security

You can additionally make security assertions in your running code. Programmatic security in Zope allows you to do this. For example, you may make a decision in DTML code to show users with the Manager role a certain bit of HTML that isn't show to users with the Anonymous role by placing an "if" statement in the code.

Programmatic security can be difficult to maintain, and should be used sparingly when you're developing under Zope. A good rule of thumb is to use declarative security wherever possible, only resorting to programmatic security when absolutely necessary.

Usually, programmatic security makes use of the same facilities available to declarative security; namely roles, permissions, and users. However, it's quite possible to completely forego the use of Zope declarative security and concoct your very own security scheme using programmatic security. This is not recommended.

Zope's Security Policy

To make use of Zope's declarative security features in your Product, you need to understand the role of the default Zope security policy. In short, the Zope security policy is:

  • access to an object which does not have any associated security information is denied by default
  • if an object is associated with a permission, access is granted or denied based on the user's roles and the roles that have been associated with that permission
  • if the object has a security assertion that it is public then access will be granted
  • if the object has a security assertion that it is private then access will be denied
  • access to objects that have names beginning with the underscore character _ is denied

All objects that need to be usable from restricted code need to have security assertions that tell the security system how to protect them. When developing an add-on Zope component, it is the developer's job to make security assertions on her objects to be sure they are properly protected.

Implementing Security

There are two types of development in which security becomes important: during the development of Python "disk-based" Products and during the development of ZClass-based "through the web products". These are covered separately below.

Implementing Security in your Disk-Based Python Product

Web Publishing Security

If a method which is part of a Product class does not declare a Python "docstring", the method may not be traversed to via Zope's object publishing machinery, regardless of its other security assertions. This essentially means it cannot be addressed via a URL directly.

Security Assertions

A developer may make several kinds of security assertions at the Python level. He does this to declare accessibility of methods and subobjects of his classes. Three of the most common assertions that developers want to make on their objects are:

  • this object is public (always accessible)
  • this object is private (not accessible by restricted code or by URL traversal)
  • this object is protected by a specific permission

There are a few other kinds of security assertions that are much less frequently used but may be needed in some cases:

  • asserting that access to subobjects that do not have explicit security information should be allowed rather than denied.
  • asserting what sort of protection should be used when determining access to an object itself rather than a particular method of the object

In major versions of Zope prior to 2.3, these security assertions were spelled quite differently. The "old" assertion spelling used a mapping structure named "__ac_permissions__" to declare protected methods. It used the astonishingly-named attribute "__allow_access_to_unprotected_subobjects__" to declare default-allow behavior. These assertion spellings are not covered in this document as they are deprecated in favor of shorter, easier, and saner spellings.

It is important to understand that security assertions made in your Product code do not limit the ability of the code that the assertion protects. Assertions only protect access to this code. The code which constitutes the body of a protected, private, or public method of a class defined in a Zope disk-based Product runs completely unrestricted, and is not subject to security contstraints of any kind within Zope.

SecurityInfo Objects

As a Python developer, you make security assertions in your Python classes using SecurityInfo objects. A SecurityInfo object provides the interface for making security assertions about an object in Zope.

The convention of placing security declarations inside Python code may at first seem a little strange if you're used to "plain old Python" which has no notion at all of security declarations. But because Zope provides the ability to make these security assertions at such a low level, the feature is ubiquitous throughout Zope, making it easy to make these declarations once in your code, usable site-wide without much effort.

ClassSecurityInfo Objects

The most common kind of SecurityInfo you will use as a component developer is the ClassSecurityInfo object.

When writing the classes in your product, you create a ClassSecurityInfo instance within each class that needs to play with the security model. You then use the ClassSecurityInfo object to make assertions about your class, its subobjects and its methods.

The ClassSecurityInfo class is defined in the !AccessControl package of the Zope framework. In order to make use of it, instantiate a ClassSecurityInfo in the class namespace of a class definition and assign it to the name security.

The name security is used for consistency and for the benefit of new component authors, who often learn from looking at other people's code. **You do not have to use the name security for the security infrastructure to recognize your assertion information, but it is recommended as a convention**. For example:

      from !AccessControl import !ClassSecurityInfo

      class Mailbox(!ObjectManager):
        """A mailbox object that contains mail message objects."""

        # Create a SecurityInfo for this class. We will use this 
        # in the rest of our class definition to make security 
        # assertions.
        security = ClassSecurityInfo()

        # Here is an example of a security assertion. We are 
        # declaring that access to messageCount is public.
        security.declarePublic('messageCount')

        def messageCount(self):
          """Return a count of messages."""
          return len(self._messages)

Note that in the example above we called the declarePublic method of the ClassSecurityInfo instance to declare that access to the messageCount method be public. To make security assertions for your object, you just call the appropriate methods of the ClassSecurityInfo object, passing the appropriate information for the assertion you are making.

ClassSecurityInfo approach has a number of benefits. It allows your security assertions to appear in your code near the objects they protect, which makes it easier to assess the state of protection of your code at a glance. The ClassSecurityInfo interface also allows you as a component developer to ignore the implementation details in the security infrastructure and protects you from future changes in those implementation details.

Let's expand on the example above and see how to make the most common security assertions using the SecurityInfo interface.

To assert that a method is public (anyone may call it) you may call the declarePublic method of the SecurityInfo object, passing the name of the method or subobject that you are making the assertion on:

      security.declarePublic(methodName)

To assert that a method is private you call the declarePrivate method of the SecurityInfo object, passing the name of the method or subobject that you are making the assertion on:

      security.declarePrivate(methodName)

To assert that a method or subobject is protected by a particular permission, you call the declareProtected method of the SecurityInfo object, passing a permission name and the name of a method to be protected by that permission:

      security.declareProtected(permissionName, methodName)

Let's look at an expanded version of our Mailbox example that makes use of each of these types of security assertions:

      from !AccessControl import !ClassSecurityInfo
      import Globals

      class Mailbox(!ObjectManager):
        """A mailbox object."""

        # Create a SecurityInfo for this class
        security = ClassSecurityInfo()

        security.declareProtected('View management screens', 'manage')
        manage=HTMLFile('mailbox_manage', globals())

        security.declarePublic('messageCount')
        def messageCount(self):
          """Return a count of messages."""
          return len(self._messages)

        # protect 'listMessages' with the 'View Mailbox' permission
        security.declareProtected('View Mailbox', 'listMessages')

        def listMessages(self):
          """Return a sequence of message objects."""
          return self._messages[:]

        security.declarePrivate('getMessages')
        def getMessages(self):
          self._messages=GoGetEm()
          return self._messages

      # call this to initialize framework classes, which
      # does the right thing with the security assertions.
      Globals.InitializeClass(Mailbox)

Important: Note the last line in the example. In order for security assertions to be correctly applied to your class, you must call the global class initializer for all classes that have security information. This is very important - the global initializer does the dirty work required to ensure that your object is protected correctly based on the security assertions that you have made. The initializer can be treated as a "black box" by the programmer - its will take care of protecting things correctly based on your security assertions. The global class initializer is located in the Globals module.

Object Assertions

Often you will also want to make a security assertion on the object itself. This is important for cases where your objects may be accessed in a restricted environment such as DTML. Consider the example DTML code:

      <dtml-var "some_method(someObject)">

Here we are trying to call some_method, passing the object someObject. When this is evaluted in the restricted DTML environment, the security policy will attempt to validate access to both some_method and someObject. We've seen how to make assertions on methods - but in the case of someObject we are not trying to access any particular method, but rather the object itself (to pass it to some_method). Because the security machinery will try to validate access to someObject, we need a way to let the security machinery know how to handle access to the object itself in addition to protecting its methods.

To make security assertions that apply to the object itself you call methods on the SecurityInfo object that are analagous to the three that we have already seen:

      security.declareObjectPublic()

      security.declareObjectPrivate()

      security.declareObjectProtected(permissionName)

The meaning of these methods is the same as for the method variety, except that the assertion is made on the object itself.

Here is the updated Mailbox example, with the addition of a security assertion that protects access to the object itself with the View Mailbox permission:

      from !AccessControl import !ClassSecurityInfo
      import Globals

      class Mailbox(!ObjectManager):
        """A mailbox object."""

        # Create a SecurityInfo for this class
        security = ClassSecurityInfo()

        # Set security for the object itself
        security.declareObjectProtected('View Mailbox')

        security.declareProtected('View management screens', 'manage')
        manage=HTMLFile('mailbox_manage', globals())

        security.declarePublic('messageCount')
        def messageCount(self):
          """Return a count of messages."""
          return len(self._messages)

        # protect 'listMessages' with the 'View Mailbox' permission
        security.declareProtected('View Mailbox', 'listMessages')

        def listMessages(self):
          """Return a sequence of message objects."""
          return self._messages[:]

        security.declarePrivate('getMessages')
        def getMessages(self):
          self._messages=GoGetEm()
          return self._messages

      # call this to initialize framework classes, which
      # does the right thing with the security assertions.
      Globals.InitializeClass(Mailbox)

Other Assertions

The SecurityInfo interface also supports the less common security assertions noted earlier in this document.

To assert that access to subobjects that do not have explicit security information should be allowed rather than denied by the security policy, use:

      security.setDefaultAccess("allow")

Note - this assertion should be used with caution. It will effectively change the access policy to "allow-by-default" for all attributes in your object instance (not just class attributes) that are not protected by explicit assertions.

This includes attributes that are simple Python types as well as methods without explicit protection. This is important because some mutable Python types (lists, dicts) can then be modified by restricted code. Setting default access to "allow" also affects attributes that may be defined by the base classes of your class, which can lead to security holes if you are not sure that the attributes of your base classes are safe to access.

Setting the default access to "allow" should only be done if you are sure that all of the attributes of your object are safe to access, since the current architecture does not support using explicit security assertions on non-method attributes.

Setting Default Roles For Permissions

When defining operations that are protected by permissions, one thing you commonly want to do is to arrange for certain roles to be associated with a particular permission by default for for instances of your object.

For example, say you are creating a News Item object. You want Anonymous users to have the ability to view news items by default; you don't want the site manager to have to explicitly change the security settings for each News Item just to give the Anonymous role View permission.

What you want as a programmer is a way to specify that certain roles should have certain permissions by default on instances of your object, so that your objects have sensible and useful security settings at the time they are created. Site managers can always change those settings if they need to, but you can make life easier for the site manager by setting up defaults that cover the common case by default.

As we saw earlier, the SecurityInfo interface provided a way to associate methods with permissions. It also provides a way to associate a permission with a set of default roles that should have that permission on instances of your object.

To associate a permission with one or more roles, use the following:

      security.setPermissionDefault(permissionName, rolesList)

The permissionName argument should be the name of a permission that you have used in your object and rolesList should be a sequence (tuple or list) of role names that should be associated with permissionName by default on instances of your object.

Note that it is not always necessary to use this method. All permissions for which you did not set defaults using setPermissionDefault are assumed to have a single default role of Manager.

The setPermissionDefault method of the SecurityInfo object should be called only once for any given permission name.

Here is our our Mailbox example, updated to associate the View Mailbox permission with the roles Manager and Mailbox Owner by default:

      from !AccessControl import !ClassSecurityInfo
      import Globals

      class Mailbox(!ObjectManager):
        """A mailbox object."""

        # Create a SecurityInfo for this class
        security = ClassSecurityInfo()

        # Set security for the object itself
        security.declareObjectProtected('View Mailbox')

        security.declareProtected('View management screens', 'manage')
        manage=HTMLFile('mailbox_manage', globals())

        security.declarePublic('messageCount')
        def messageCount(self):
          """Return a count of messages."""
          return len(self._messages)

        security.declareProtected('View Mailbox', 'listMessages')
        def listMessages(self):
          """Return a sequence of message objects."""
          return self._messages[:]

        security.setPermissionDefault('View Mailbox', ('Manager', 'Mailbox Owner'))

      # call this to initialize framework classes, which
      # does the right thing with the security assertions.
      Globals.InitializeClass(Mailbox)

What Can (And Cannot) Be Protected?

It is important to note what can and cannot be protected using the SecurityInfo interface. First, the security policy relies on Acquisition to aggregate access control information, so any class that needs to work in the security policy must have either Acquisition.Implicit or Acquisition.Explicit in its base class hierarchy.

The current security policy supports protection of methods and protection of subobjects that are instances. It does not currently support protection of simple attributes of basic Python types (strings, ints, lists, dictionaries). For instance:

      from !AccessControl import !ClassSecurityInfo
      import Globals

      # We subclass ObjectManager, which has Acquisition in its
      # base class hierarchy, so we can use SecurityInfo.

      class MyClass(!ObjectManager):
        """example class"""

        # Create a SecurityInfo for this class
        security = ClassSecurityInfo()

        # Set security for the object itself
        security.declareObjectProtected('View')

        # This is ok, because subObject is an instance
        security.declareProtected('View management screens', 'subObject')
        subObject=MySubObject()

        # This is ok, because sayHello is a method
        security.declarePublic('sayHello')
        def sayHello(self):
          """Return a greeting."""
          return "hello!"

        # This will not work, because foobar is not a method
        # or an instance - it is a standard Python type
        security.declarePublic('foobar')
        foobar='some string'

Keep this in mind when designing your classes. If you need simple attributes of your objects to be accessible (say via DTML), then you need to use the setDefaultAccess method of SecurityInfo in your class to allow this (see the note above about the security implications of this). In general, it is always best to expose the functionality of your objects through methods rather than exposing attributes directly.

Note also that the actual SecurityInfo instance you use to make security assertions is implemented such that it is never accessible from restricted code or through the Web (no action on the part of the programmer is required to protect it).

Scope Of Security Assertions

Security assertions that you make using ClassSecurityInfo objects affect instances of that class. You only need to make security assertions 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 assertions made in the base classes themselves. The only time you would need to make a security assertion 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. For example:

      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'))

      # call this to initialize framework classes, which
      # does the right thing with the security assertions.
      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')

      # call this to initialize framework classes, which
      # does the right thing with the security assertions.
      Globals.InitializeClass(Mailbox)

When Should I Use Security Assertions?

If you are building an object that will be used from DTML or other restricted code, or that will be accessible directly through the web (or other remote protocols such as FTP or WebDAV) then you need to define security information for your object.

Note that this includes objects that are returned from Python Scripts or External Methods. for example, you have an External Method that returns instances of a custom Book class. If you want to call it from DTML to work with the returned Book instances, you will need to ensure that your class supports Acquisition, make security assertions on the Book class and initialize it with the global class initializer (just as you would with a class defined in an add-on component). For example:

      # an external method that returns Book instances

      from Acquistion import Implicit
      import Globals

      class Book(Implicit):

        def __init__(self, title):
          self._title=title

        # Create a SecurityInfo for this class
        security = ClassSecurityInfo()
        security.declareObjectPublic()

        security.declarePublic('getTitle')
        def getTitle(self):
          return self._title

      Globals.InitializeClass(Book)

      # The actual external method
      def GetBooks(self):
        books=[]
        books.append(Book('King Lear'))
        books.append(Book('Romeo and Juliet'))
        books.append(Book('The Tempest'))
        return books

Note: classes defined in external methods cannot be made persistent. They can be created "on-the-fly" and returned to a caller, but they cannot be subsequently stored within the ZODB. Storing an instance of a class defined within an external method in the ZODB has an undefined result.

One benefit of the SecurityInfo approach is that it is relatively easy to subclass and add security info to classes that you did not write. For example, in an External Method you may want to return instances of Book, but Book is defined in another module out of your direct control. You can still use SecurityInfo to define security information for the class by using:

      # an external method that returns Book instances

      from Acquisition import Implicit
      import bookstuff
      import Globals

      class Book(Implicit, bookstuff.Book):
        security = ClassSecurityInfo()
        security.declareObjectPublic()
        security.declarePublic('getTitle')

      Globals.InitializeClass(Book)

      # The actual external method
      def GetBooks(self):
        books=[]
        books.append(Book('King Lear'))
        books.append(Book('Romeo and Juliet'))
        books.append(Book('The Tempest'))
        return books

Designing For Security

Security Is Hard (as Jim likes to say), and there is a fair amount of high-fiber material in this document. As a component developer, following these basic guidelines will go a long way toward avoiding problems with security integration. They also make a good debugging checklist!

  • Ensure that any class that needs to work with security has Acquisition.Implicit or Acquisition.Explicit somewhere in its base class hierarchy
  • Design the interface to your objects around methods
  • Ensure that all methods meant for use by restricted code have been protected with appropriate security assertions
  • Ensure that you called the global class initializer on all classes that need to work with security

Compatibility

The implementation of the security assertions and SecurityInfo interfaces described in this document are available in Zope 2.3 and higher.

Although we highly encourage component developers to use the security assertion system documented here, Zope components that do not use the new SecurityInfo interfaces for security assertions (ones which use older mechanisms) will continue to work without modification for some time. The internals that the SecurityInfo interfaces now hide may change in the future, but we will give component developers some warning before this happens.

  • Declarative security
    • ClassSecurityInfo
    • ModuleSecurityInfo

Using protected components

XXX

Security Policies

XXX

  • Using RoleManager base class to allow users to control security policies on your products