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
orAcquisition.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