You are not logged in Log in Join
You are here: Home » Members » mcdonc » PDG » 6-1-Security.stx » View Document

Log in
Name

Password

 

6-1-Security.stx

Zope 2.2 Product Developer's Guide - Chapter VI - Zope 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 often confusing-- set of security features. This chapter's goal is to shed a little light on Zope security in the context of Product development.

We're going to take a bottom-up view of Zope security in this chapter. We won't actually get to implementing security in a Product until near its end. The first ten sections of this chapter will give us some context by exposing us to the Zope security model in abstract, though we'll throw in a little bit of coding along the way just for fun and demonstration purposes. The remaining sections will concentrate on how to implement Product security in the context of sample applications for ZClasses and Python-based products.

NOTE: Much of the introductory material covered in this document is also covered in the O'Reilly Zope Book.

Declarative and Programmatic Security

Security within Zope is defined by users, user folders, authentication credentials, roles, permissions, and ownership. These are the facilities that allow you to secure the 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. All of these security features can be used both declaratively and programatically* during Product development.

Zope declarative security is accomplished through the manipulation of permissions and roles. All Zope objects may be protected from unauthorized access "ahead of time" by providing permission settings to objects and by assigning roles to Zope users. For example, I might change security settings of Zope objects related to a Product I've developed in order to assert that "only users with the Manager role can view a particular folder or its subobjects." Declarative security in Zope is roughly analogous to assigning files on a UNIX filesystem permission bit settings and user group memberships. Declarative security allows me to secure Zope object independently of my application code.

Programmatic security in Zope means that I can perform security assertions in my running code. For example, I might make a decision in DTML code to show users with the Manager role a certain bit of HTML that I don't show to users with the Anonymous role. 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.

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. They are useful to describe what we're going to try to do a little later in this chapter, however.

The Superuser

Users play a big part in Zope security. But before we can talk about Zope users in general, we need to understand a few things about the Zope "superuser". When Zope is first installed, you may access the Zope management interface using an externally-defined superuser account. The the Zope superuser user is defined in the $INSTANCE_HOME/access file (FYI: $INSTANCE_HOME always refers to the "main" Zope directory), externally to any other Zope authentication data repository. It cannot be managed like other Zope users. Instead, you need to directly edit the access file or use the zpasswd.py utility included with Zope to change the superuser password and username.

Curiously, one of the first things a Zope installer needs to do is to define additional management accounts with which he or she can actually add content, because the superuser does not have the power to define content on the site. The additional management accounts created during this "bootstrapping" process effectively become more powerful than the superuser itself.

You say, "More powerful than the superuser? How can a normal user be more powerful than the all-powerful superuser? That's contrary to all that is good and holy." Well, personally I can't say that I disagree with you. Nonetheless, in Zope speak, the superuser user is special, and may only be used to "bootstrap" the system, manage other users, and fix improperly-set permissions on objects within the site. It may not be used to create, edit, copy or move objects, and thus is useless for development or content management purposes.

This possibly unfamiliar definition of "superuser" is necessitated by the fact that all web-defined objects in Zope which are "executable" (DTML Methods, External Methods, Python Methods, etc.) are executed with the effective permissions defined by the intersection of the privileges of the user who "owns" the object (generally the content creator) and the privileges of the person "executing" the object (the site visitor). The superuser, despite being crippled from a content management perspective, is actually very powerful. It has the ability to traverse the site without restriction or to manage authentication and permission settings. Therefore, the creation of content by the superuser is restricted. This ensures that you don't accidentally "shoot yourself in the foot" by using the superuser account to browse a Zope site which contains malicious scripts contributed by persons with lesser privilege. We'll get to the concepts of execution and ownership a little further in this chapter, but just trust me for now, and know that the superuser is purposely crippled as far as content management activity goes. In future versions of Zope, the superuser account may be renamed to something more intuitive, like bootstrapuser or somesuch, but for now you'll need to deal with the unfamiliarity as best you can.

Thus, all Zope installations must contain at least one defacto "management" account which is added at installation time and which has effectively more privilege from a content management perspective than the superuser itself. This is an enforcement of the "don't do anything as root or Administrator that you don't absolutely need to" precept honored by most competent UNIX and Windows NT system administrators. Though you probably won't be administering all (or possibly any of) the sites in which your Product is installed, it's helpful to understand the role of the Zope superuser when your customers are experiencing problems attempting to install your Product as the superuser. They can't. Tell them to define a management user, and then walk them through the process of installing your product as that management user. Here's what they might see if they try to perform content management or object creation duties (such as adding or changing instances of your Product) as the superuser:

Figure 6-0 - Superuser cannot own

User Folders

"User folders" are authentication data repositories which contain Zope user data. Defining user folders is not a step that a Product developer is required to take during development. Generally, user folder definitions will be handled by the content manager, a webmaster or another delegate whom installs your Product, and he or she will be responsible for the ongoing task of maintaining users. However, it's crucial for developers to understand the role of user folders in the Zope security machinery, how user folders relate to roles, and how multiple user folders within the same site relate to each other. As a Product developer, you may even be required to code your own user folder implementation (for example, to match an existing cookie-based authentication scheme), so it's wise to have at least a passing understanding of what is going on "under the hood".

When Zope is installed for the first time, an object named acl_users is automatically created at the root of your Zope "instance space." (NOTE: When I say "instance space" I broadly refer to all parts of Zope outside the Control Panel viewable from within the management interface. This distinction will become important later, when we talk about "class space" in relation to ZClasses.) If you've poked around inside the Zope management interface a little bit you may have discovered that you can add and remove user definitions using the management facility that the acl_users object exposes. If not, go look inside it now. Here's what mine looks like:

Figure 6-1 - My acl_users management interface

If I click on the chrism user, it allows me to edit the user definition:

Figure 6-2 - The chrism user being edited

This acl_users object is an instance of a "user folder". A user folder, in Zope terms, is a "special" Zope object which you can use to define and manage the definitions of your users: their names, their respective roles, and various other attributes such as domain restrictions. Users are defined in Zope by their presence in a user folder within the Zope instance space. User folder instances always have a Zope id of acl_users, a naming convention that is followed by all user folders. User folders are not renamable, and cannot be cut and pasted between sections of a site, although they can be imported and exported.

NOTE: It's beneficial to know for purposes of development and research that the terms "user folder", "acl_users", "user database" (and, rarely, the more obscure "__allow_groups__") are more or less equivalent in Zope terminology. In this discussion, I even make the conscious mistake of giving them a new name, "authentication data repositories," for purposes of descriptive accuracy. But just know that user folders are an integral part of Zope security, whatever they may be called, and that while within the Zope web management interface, if you run across a user folder, it will always be named "acl_users". It's also useful to know that no matter what kind of user folder you're working with (there are several different kinds, such as a GenericUserFolder, smbUserFolder, etcUserFolder, LoginManager, UserDb, and a multitude of others serving niche needs), each exposes more or less the same user folder API, which makes them more or less interchangeable as long as you heed the interface. (Note: Currently there is no consensus on what should make up a User Folder API. This is slightly embarassing, but it's true. The Interfaces Wiki on Zope.org has more information on what a User Folder API should look like.)

When I first installed Zope and logged on as the superuser, I defined a (handsome and charming) chrism user within this particular user folder, and thus he shows up in Fig 6-1 and can be edited ala Fig 6-2. Additionally, as the superuser, I granted chrism the Manager role within this user folder, and therefore chrism has a great deal of power over the system at a very high level. This level of power is typical of that possessed by a "site manager". Because as superuser, I granted chrism the Manager role in the "root" user folder, chrism is effectively the most powerful (and handsome) user on this particular Zope system, arguably possessing even more content management power than the superuser user itself, as explained in the preceding section.

Though multiple user folders may exist in a single Zope site, only a single user folder may exist within any given Zope Folder. If you try to add an additional user folder to a Folder which already contains a user folder, you'll see something like this:

Figure 6-3 - Cannot add more than one user folder to a single Folder

Despite this limitation, more than one user folder can exist in a Zope site. Because my chrism user has the privilege to do so, I'll go create a Marketing folder, and I'll create another acl_users user folder object inside of it:

Figure 6-4 - Adding a user folder to a different Folder

There are good reasons why I'd want to create a Marketing Folder object. Maybe I want to put some marketing content in there to partition my site. But why would I want to create another user folder inside the Marketing Folder? The answer is delegation. Zope has been engineered with the goal of providing services to "customers who have customers," such as newspaper companies who manage section editors whom, in turn, manage columnists. Instead of providing a single monolithic authentication repository, Zope allows content managers to delegate the power to create and manage additional authentication repositories to other users. These additional authentication repositories (user folders) can be managed by the site manager, the authentication repository creator, or by an arbitrary user to whom the site manager or repository creator chooses to delegate this power. Users created within this authentication repository have privileges which only extend to the Folder in which the repository was created and "below".

In my case, I've created a Marketing Folder which contains a user folder. I may now define a user within the Marketing Folder's user folder and grant that user the ability to manage additional users only within the Marketing Folder's user folder. I'll do so by creating a user, jed, and I'll give him the power manage users within the Marketing user folder by assigning him the Manager role, as well as the Marketing role, which is a new role which I created in the Security tab of the root folder.

Figure 6-5 - jed added to the Marketing user folder

Figure 6-6 - roles provided to jed in the Marketing user folder

Via the Zope management interface, arbitrary users with appropriate permission can define additional Folders. Within these, they may define additional user folders, and thus may define users and dole out roles to the users they create as they see fit. The users which are defined in that round of delegation may then themselves choose to define Folders which contain user folders, in which they can delegate roles to other people, and so on, and so on, ad infinitum. Roles are ways of granting power to users on a granular level. They will be discussed in a later section.

In our example, Jed is free to define a new Folder within the Marketing Folder which itself contains a user folder, and he may define and grant access to users within this Folder just as I did as chrism when I created the Marketing Folder and defined jed in the Marketing user folder.

Jed can make full use of the Zope management interface to delegate power to his co-workers as he sees fit without needing to consult chrism the (good-looking, devilish) site manager. Jed's co-workers, if he has defined their users in the Marketing Folder and granted those users the appropriate roles, may also create additional Folders within the "Marketing" Folder, and may then create user folders within them, extending the delegation.

This lack of decentralization tends to gets a little mind-boggling, but the goal is to give "more power to more people," a central Zope tenet. It's important to understand that control flows "down" from the Zope root folder. Although Jed has Manager privileges in the Marketing folder and in folders "below" the Marketing folder, he does not have Manager privileges "above" within the "root" folder (indeed he has no privileges at all), because chrism didn't extend to him those privileges. This means that Jed can control and delegate the control of objects created within the Marketing folder and its subfolders, but he cannot change or delegate control of objects created within the "root" folder. He can't even use the management interface in the "root" folder. Only the chrism and superuser users have the power to do this in this particular case.

As an example, let's try to understand what happens when Jed tries to access the management interface of the "root" folder by visiting http://localhost:8080/manage. While logged in as Jed, I'll try it:

Figure 6-7 - Jed trying to hack his way in to the root mgmt interface

Jed was denied access to the management interface of the root folder. But why is this? It's because there is no user defined as jed in the root user folder.

While attempting to authenticate and authorize users, the Zope security machinery checks the "closest" user folder first, working its way "upwards" through the Zope user folder definitions via acquisition if it cannot find a user folder in the same Folder as the object being accessed. In this case, the "closest" user folder is the root user folder, because we accessed the "root" Folder via the http://localhost:8080/manage URL (remember that the "/" of your site is almost always the "root" Folder, and that manage is a method defined on it). Since the jed user is not defined in the root user folder, the security machinery raises an "Unauthorized" message and denies access.

If there had been a jed user in the root user folder, and if that user had been granted the Manager role, and if the password defined for that user matched the authentication credentials sent by the browser (left over from Jed's login to the Marketing Folder), Jed would have been granted access to manage objects in the root folder. Likewise, if Jed knew the chrism or superuser username and password, he could enter it at a following browser authentication prompt to get access.

Look at the next figure, in which the chrism user seems to have no difficulty at all in accessing the Marketing user folder:

Figure 6-8 - rugged chrism user visits the Marketing User Folder

Because the chrism user has the Manager role within the "root" user folder. he can manage objects in the root folder and in all folders below. He can also add users to the Marketing user folder, if he so desires.

The magic that causes this to happen is acquisition and delegation. Within the code shared between the Zope publishing machinery, the actual user object which represents a Zope user, and the user folder in the Marketing folder that is visited when a web request comes in to http://localhost:8080/Marketing/manage, the set of decisions made by Zope are:

  • Since the manage method of the Marketing folder is protected by a permission which does not allow anonymous access, obtain user credentials from the request to ensure the user is authorized to access the management screen.
  • Since the Marketing folder contains a user folder, use it to obtain the visitor's credentials from the request.
  • If the Marketing user folder has a user definition with the username provided by the request, check the password against the one it knows for this username. If it doesn't match, raise an error indicating the user is unauthorized. If it does match, let the user in and give him the roles that are defined for him within the Marketing user folder.
  • If the Marketing user folder doesn't have a user definition for the username provided by the request, delegate the authentication request to the "closest" user folder via acquisition. In this case, the "closest" user folder is the "root" user folder.

This means that as the chrism user, when visiting the Marketing folder, credentials are first checked against the user definitions in the Marketing user folder, and when it decides that chrism user doesn't exist in itself, it delegates the authentication procedure to the "root" user folder which finally authenticates chrism and provides chrism with the Manager role. This allows me to manage objects both in the Marketing Folder and the "root" Folder when I log in as chrism. This "chaining" of authentication checks happens every time you access a Zope object via a web (or FTP) request.

The user folder created automagically during Zope installation is known as the "root user folder", and it's a good idea not to mess with it too much other than to define top-level users. Though you can replace it with a different kind of user folder, the procedure to do so is arcane and difficult. Instead of attempting to replace it, it's probably a good idea to define additional user folders that live in different Folders "below" the root.

Note, however, that the definition of many user folders within a site is almost always a bad idea if you don't really need them because authentication can become quite complicated and may require advanced understanding of how acquisition works and intimate knowledge of the implementation of various user folder instances. It's best to stick to the KISS principle ("Keep It Simple, Stupid"). If you can get by with a single user folder, do so in your production environment. But while developing your Products, keep in mind that the folks using your Product may require multiple or arbitrary user folder instances, and may expect your Product to work with all and any of them.

Let's see some code that operates on a User Folder. It's a silly example, but bear with me. Let's create a DTML method with an id of user_name and a body of:

     <dtml-with acl_users>
       <dtml-var expr="getUser('chrism').getUserName()">
     </dtml-with>

If the user which executes this code has the appropriate set of roles (generally "Manager-level" roles), he or she will see that the chrism user has the username chrism when this DTML method is viewed (I told you it was a stupid example). Let's try it in Python for the heck of it (you can use an External Method or a Python Method for this):

     def get_roles(self):
         """ returns 'chrism' """
         return self.acl_users.getUser('chrism').getUserName():

This is not particularly interesting or useful, but hopefully you get a flavor of how the user folder object is employed. For other interesting ways to use user folders, see the user folder API documentation.

We've covered a lot of ground on user folders. We've found out that user folders are authentication data repositories, and that they may be referred to using a number of different terms. We've found that user folders are web-manageable, and allow site managers to specify user names, roles, and domain specifications. We've found that there are many different kinds of user folders, and that multiple user folders can exist within a single Zope installation. We've also found that user folders participate in the Zope acquisition machinery and delegate authentication duties to each other through this machinery.

Authentication Credentials

Consider the typical web request. Joe Bloggs hits your Zope-powered website from Internet Explorer on his new Pentium XI which he just purchased in a crack-and-dent sale from Best Buy. It is probably safe to assume that you don't want Joe to be visiting the Zope management interface, futzing with your instance data, overwriting rows in your database tables, or making modifications to your Product code. However, you, as a busy software professional or content manager may need to use a computer at an Internet cafe or you may even want to use Joe's new $1800.00 Packard Bell to do just those sorts of things. Therein lies a paradox.

One of the strange and wonderful --and potentially dangerous-- things about Zope is that all users are treated more or less like "first class citizens". Possessing the proper credentials, a user coming in from just about anywhere (minus domain restrictions, of course) can manage the objects that make up your Zope-powered site using the Zope web management interface. If you'd like to manage Zope from an Internet cafe, it's really no problem. You need not ask permission to install special software on the cafe machine. Instead, just append /manage to the URL from which you want to start the management inteface in within a browser and log in as a privileged user. But it's in your best interest to understand how Zope comes to possess the credentials of a requester, so Joe Bloggs doesn't end up hosing your carefully crafted site when he appends /manage to the same URL.

Zope may obtain the requester's credentials as data in an HTTP header or as part of a cookie sent by a browser. Which one of these methods does your Zope use? Probably HTTP basic authentication unless you've replaced the stock user folder object in the root of your Zope installation with another or you've extended Zope by installing and instantiating a different user folder Product which employs a different credentials-gathering scheme. The user folder that is installed automatically by Zope in the root folder is an instance of a Standard user folder, which is an object that uses the ZODB to store username, password, and domain data and which uses the "HTTP basic authentication" scheme (see RFC 2068) to obtain user credentials.

Authentication credentials are exposed to Zope as a result of a visitor attempting to access a "protected" area of your site. When the visitor hits a URL within your Zope object space for the first time, if the method of the object that the URL represents is inaccessible by the "anonymous user", the Zope publishing machinery asks the "closest" user folder --the user folder in the same container as the accessed object or via acquisition-- to authenticate the request. User folders in general, and the Standard user folder in particular, examine information passed in from the publishing machinery to determine the username and password which the visitor presented to Zope. If this username and password can be validated by the user folder, and if the roles assigned to the user permit they type of access required by the request, Zope permits access and carries out the transaction requested.

It's important to know that full authorization checks do not happen if the method represented by the URL is accessible by an "anonymous user". Instead, Zope short-circuits the authorization procedure and just carries out the transaction represented by the request, populating the AUTHENTICATED_USER attribute of the REQUEST object with Anonymous User. This can bite you sometimes, especially when performing programmatic security checks. If your method does not require authentication or authorization, your user will be considered the Anonymous User even if he or she had previously logged in to the site as a result of his or her interaction with a method that required authentication and authorization.

Though there are other methods of obtaining user credentials, such as cookie-based authentication schemes, the Standard user folder implementation can only obtain user credentials using the HTTP basic authentication scheme. Other user folder implementations (such as etcUserFolder) support different methods of obtaining user authentication credentials. They will not be discussed here, and we will concentrate on how Zope can be made to authenticate users against a Standard user folder using HTTP basic authentication.

HTTP basic authentication is a very basic authentication scheme formalized in 1996 by the W3. HTTP basic authentication makes it possible for a web server to authenticate and authorize users who employ a user agent (a browser) which complies with the basic authentication specifications. All of the commercially popular browsers support HTTP basic authentication. To describe its operation, and to give you some understanding of what's going on "under the hood" of the browser and within Zope, I'll give you a low-level blow-by-blow description of what happens when our fictitious user "Jed" opens a new browser window and visits the /Marketing/manage URL:

  • Jed turns on his computer and opens Netscape Communicator.
  • Jed types in to his browser address bar http://www.azopeserver.com:8080/Marketing/manage
  • Jed's browser connects with the Zope server's TCP/IP port 8080, and issues the following request to Zope:
          GET /Marketing/manage HTTP/1.1
    

This request indicates that Jed's browser wants to render the document which resides at /Marketing/manage, and that it wants to use the HTTP 1.1 protocol (see RFC 2068).

  • Since Zope has no idea at this point who the requester is, *and because the /manage method of the root Folder is protected by permissions and roles and is not accessible by an anonymous user*, it responds with this bit of text:
          HTTP/1.1 401 Unauthorized
          Server: Zope/(unreleased version) ZServer/1.1b1
          Date: Mon, 24 Jul 2000 01:55:51 GMT
          Www-Authenticate: basic realm="Zope"
          Content-Type: text/html
          Content-Length: 1404
    
          [.. a bunch of error stuff in HTML ..]
    

Note the line Www-Authenticate: basic realm="Zope". This is the important bit.

  • Jed's browser, instead of rendering the HTML that I've shortened to [.. a bunch of error stuff in HTML ..] for purposes of brevity, notices the Www-Authenticate line and pops up an authentication dialog box.
  • Jed types jed as his username, enters his (correct) password, and presses Enter.
  • Jed's browser sends another GET request for the /Marketing/manage page, this time slightly modified:
            GET /Marketing/manage HTTP/1.1
            Authorization: Basic amVkOmplZA==
    
          Note new line 'Authorization: Basic' followed by the string
          'amVkOmplZA=='.  Jed's browser is telling us that it wants to
          authorize against the '/Marketing/manage' URL.  The strange line
          noise in there is the base64 encoded string "jed:jed", which
          represents Jed's username ('jed') and password ('jed') separated
          by a colon.  The username and password are not encrypted in any
          way, and despite being base64 encoded can essentially be thought
          of as being passed "in the clear."
    
        -  The 'Marketing' Folder's management screen is displayed in
           Jed's browser.
    
       After this first bit of back-and-forth, Jed's browser "remembers"
       the '/Marketing/manage' URL and *sends his username and password
       with every subsequent request to that URL and sub-URLs (such as
       '/Marketing/manage/foo') related to it when requested via the
       Www-Authenticate header provided by the server.* The user folder at
       '/Marketing/manage/acl_users' recognizes Jed by his credentials,
       which are passed in the authentication header of the HTTP request.
    
       This is how the browser-server combination preserves the illusion
       of "statefulness" between browser requests.  But this highlights
       some peculiarities of using HTTP basic authentication.  First,
       there is no good way to "log out." Additionally, *two* transactions
       between the server and the agent are necessary for each request
       (the server presenting the 'Www-Authenticate' challenge and the
       client responding with the 'Authorize:' response).  Each time a
       "protected" resource is encountered, the agent must re-authenticate
       itself with its cached credentials.
    
       This is a good place to interject a FAQ and FAQ answer.  Many
       beginning users and developers get confused by the apparent
       inability to "log out" of Zope once they've authenticated to a Zope
       server via HTTP basic authentication.  It's unfortunately a
       built-in limitation of HTTP basic authentication and cannot easily
       be fixed without using a different authentication scheme through a
       different user folder implementation.  To 'log out' of Zope,
       creating and viewing this DTML method *and pressing "OK"* in the
       pop-up authentication dialog box during its view works well::
    
           <dtml-raise Unauthorized>
            Unauthorized.
           </dtml-raise>
    
       I generally make a method like this in the root of my Zope
       installations and call it 'logout'.  Viewing this DTML method also
       provides developers with the opportunity to log in as a different
       user when the pop-up box is displayed.  Note that if the "cancel"
       button is pressed on the ensuing authentication dialog, the
       authenticated user will *not* be logged out.  As an additional
       method of "logging out" developers and users may also quit their
       browsers and start them again, which clears the browser of
       authentication information.  On the browser's subsequent restart,
       it will not pass any authentication information in the header until
       another authentication process has been completed.
    
       Zope supports HTTP basic authentication because it is possibly the
       most widely used form of web-based authentication on the Internet,
       despite its limitations.  If basic authentication doesn't fulfill
       your needs, you may want to try one of the other user folder
       implementations available for Zope that use different
       credentials-gathering schemes such as cookies.
    
       Hopefully you've now got an understanding of how Zope obtains user
       credentials via HTTP basic authentication.  The next few sections
       will deal with how Zope uses those credentials to enforce the
       security policy of a site.
    
      D.  Users
    
       We've been throwing the word "users" around without a definition
       for some time now.  Let's try to nail the details.  Conceptually,
       in Zope, users represent people.  In a perfect world, there would
       be a single Zope user for each person that wanted to use a single
       Zope system.  But in reality, it's also possible that a single
       person might wish to be represented by two Zope users (for
       instance, in the case that a sysadmin has a "management" account
       and a "normal" account).  It's also possible that a Zope user may
       represent more than one person (for example, in the case that a
       group of people shares a Zope account).
    
       However a user is employed, it is represented in Zope by a User
       object.  This User object *knows* things (e.g. its domain spec, the
       correct username and password for the user it represents).  The
       User object also *does* things (e.g. it provides its username to a
       caller, returns the *roles* granted to the user it represents).
       The things it "knows" are its attributes, and the things it "does"
       are its methods.  If we can grab hold of the User object that
       represents a person or group within Zope, we can do some mildly
       interesting things with it.
    
       The transient 'REQUEST' object contains a reference to the
       currently logged-in User object, which is constructed during the
       process of gathering and validating user credentials.  By coding
       DTML or Python within Zope, we can find out more about the
       currently logged in user by making use of the methods of the User
       object via the Authenticated User API (see the Zope Help System API
       Documentation section accessible via the Zope management interface
       Help button).
    
       I'm going to construct a DTML method within Zope that shows me
       information about its viewer.  I'll give it an 'id' of
       'user_roles', and give it a body like so::
    
         <dtml-with REQUEST>
           <dtml-in expr="AUTHENTICATED_USER.getRoles()">
                <dtml-var sequence-item><br>
           </dtml-in>
         </dtml-with>
    
       Here's what it looks like on my system:
    
        <img src="FIG-6-9.gif">
    
        Figure 6-9 - The 'user_roles' DTML method
    
       While I'm logged in as 'chrism', I'll view it.
    
        <img src="FIG-6-10.gif">
    
        Figure 6-10 - Viewing the 'user_roles' DTML method as 'chrism'
    
       This little DTML script iterates through the roles of the currently
       logged in user ('chrism'), and reports back each of the roles
       (which are just strings) separated by an HTML break tag.  Since the
       'chrism' user in this case only has one *role* ('Manager'), it is
       the only one reported.  The method that does the dirty work here is
       'getRoles()' of the 'AUTHENTICATED_USER' object, which is itself
       contained within the REQUEST object.  To get a better understanding
       of what is going on here, let's rewrite the DTML script another
       way::
    
        <dtml-in expr="REQUEST['AUTHENTICATED_USER'].getRoles()">
          <dtml-var sequence-item><br>
        </dtml-in>
    
       Here's one more way::
    
        <dtml-in expr="REQUEST.AUTHENTICATED_USER.getRoles()">
          <dtml-var sequence-item><br>
        </dtml-in>
    
       Let's rewrite it as an External Method in Python::
    
        def user_roles(REQUEST):
           """ return HTML representing user's roles """
           user = REQUEST['AUTHENTICATED_USER']
           roles = user.getRoles()
           html = ""
           for role in roles:
               html = html + "%s%s" % (role, "<br>")
           return html
    
       As we can see, the 'REQUEST' object contains a reference to the User
       object ('AUTHENTICATED_USER').  The User object has a method named
       'getRoles()' which provides us with a sequence of roles--which are
       strings--that have been granted to the currently logged-in user.
    
       What happens when I log out of Zope as (unbearably charming)
       'chrism' and try to run this method?  What will be returned?
    
        <img src="FIG-6-11.gif">
    
        Figure 6-11 - Visiting the 'user_roles' method without any
        credentials (e.g. from a freshly started browser)
    
       After logging out as 'chrism' and revisiting the 'user_roles'
       method as an unauthenticated user, you can see that Zope allows me
       to view the document (because it's not restricted from anonymous
       view by permission settings), and it considers me to possess the
       'Anonymous' role.  So if I have the 'Anonymous' *role* as an
       unauthenticated user, what *user* does it think I am?  I'll
       construct another DTML method that will tell us the answer to this
       burning question named 'user_id'::
    
         <dtml-with "REQUEST['AUTHENTICATED_USER']">
           <dtml-var getUserName>
         </dtml-with>
    
       <img src="FIG-6-12.gif">
    
       Figure 6-12 - Viewing the 'user_id' method as an anonymous user.
    
       When Zope has no idea who you are, as far as it's concerned, you're
       the 'Anonymous User' (*no one* expects the Anonymous User!).  The
       'Anonymous User' account is a catch-all account that has a low
       privilege level on the system.  It is not defined in any user
       folder, it is "built in" to Zope.  You'll see it sometimes referred
       to as the "nobody" user as well, by convention.  As a side-effect
       of construcing this method, we've also learned that the getUserName
       method of the User object returns the name of the currently
       logged-in user as a string.
    
       We've gone through an example of a way to programatically
       interrogate the User object which represents the currently
       logged-in user.  We've also seen the way Zope treats
       unauthenticated users.  Key points are that the User object is
       available via the transient 'REQUEST' object, that User objects
       have methods, that users without credentials possess only the
       'Anonymous' role, and the username of unauthenticated users is
       'Anonymous User'.  Consult the Authenticated User API documentation
       (see the Zope Help System API Documentation section) for the full
       story on the other methods which exist on a User object.
    

Roles and Permissions

We've found out all about users, user folders, and authentication credentials. Now we'll explore roles and permissions. These two concepts are possibly the most important parts to Zope security.

Probably the easiest way to think about the concept of roles in Zope is to compare them to the concept of groups in operating system products. For example, in UNIX, users belong to groups by virtue of their assignment to a primary group in the /etc/passwd file or to their assignment to a number of groups via NIS netgroups or a similar mechanism. In Windows NT, you can use the User Manager utility to assign users to local groups and domain groups. In Zope, roles relate almost directly to these concepts. Roles, in general, serve the purpose of providing a facility to construct groups of users.

If you're more familiar with software design engineering than operating system security, it may be helpful also to know that in Zope, roles can also be effectively used as embodiments of "actors". At Digital Creations, we construct applications for our customers using a subset of the "Rational Unified Process" (a foo-foo term for a somewhat common-sense software design and implementation process championed by the company named Rational Software). In the RUP, part of the design process is to determine the "actors" which are to make use of a proposed application. For any multiuser application, different people will need to use the application differently. For example, in an application constructed for data entry, there might be a "Data Entry Employee" actor, and a "Data Entry Systems Manager" actor, each of which would need to use the application in ways that the other might not. When constructing multiuser applications, at Digital Creations, we try to use roles to represent and classify actors within the application. Hopefully you get the idea. I'm not going to get in to the RUP further, as it's not really germane to the discussion, the topic was only introduced as a way for people familiar with software design engineering to understand Zope roles better.

In the section on User Folders, we saw how we could provide Zope users with roles via the web management interface. Let's take another look at that interface.

Figure 6-13 - The user folder management interface, viewing the chrism user.

We can see from Fig 6-13 that the chrism user has the Manager role within this user folder. What does this mean? Well, the answer is "not much" without exposing the availablity of another set of attributes that we haven't seen much, called permissions.

A role is a name that ties users to permissions. All Zope object instances may be protected in some fashion or another by permissions. Properly logging in as a user represents authentication of your identity (your user credentials) to Zope, which means Zope knows "who you are." The combination of roles and permissions represent a way to tie user authorization to the performance of actions, which means Zope can determine "what you can do" using what it knows about your roles in the context of the permissions defined on the object you're trying to access. While your user may be authenticated when logged in to Zope, your user may not be authorized to perform a certain action due to the permission settings of the object you're trying to access combined with your user's roles in the context of that object.

A permission is the smallest unit of access to an object, roughly equivalent to the atomic permissions on files seen in Windows NT or UNIX: R (Read), W(Write), X(Execute), etc. However, unlike these types of mnemonic permissions shared by all sorts of different file types in an operating system product, in Zope, a permission usually describes a fine-grained logical operation which takes place upon an object, such as View Management Screens or Add Properties. It may additionally be helpful for software developers experienced with Smalltalk (or other derived OO languages) to think of Zope permissions as "method categories", where a permission represents access granted to a user to execute a group of methods, as well as a generally convenient way to classify methods. We'll get into how methods relate to permissions a little later on in this section, it's just a tip for now.

It's important to understand that different types of objects define different types of permissions as appropriate for the purpose of the object. Permissions may be shared among objects or they may only be meaningful for one type of object. For example, many object instances may expose the "View" permission, but it's pretty likely that only an object instance that's a part of the ZHaircut product would expose the permission "Update ZHaircut." It's vitally important, as a Product developer, to understand that you must define or reuse roles and permissions for your Product's objects as a part of Product development if you wish your Product's objects to participate in the Zope security machinery.

Because permissions and roles are just names of actions represented by strings, they are easy to share among Products. You should define new permissions and roles instead of reusing system-defined ones only when you need to. For example, it would be considered bad form to create a permission named View MyFoo if you could have just used the system-defined View permission to protect the same action with the same results. However, you should define permissions and roles for actions particular to your Product which you wish to keep independent of other Products. For example, you will probably want to define an Add MyFoo permission vs. reusing an existing permission AddSomebodyElsesFoo. And you might want to reuse the Manager role instead of creating a new MyFooProductManager role in your Product if you don't need to keep the responsibilities of managing your product's independent from those of the general Manager role.

Let's take a look at a real-world list of roles and permissions through the Zope management interface:

Figure 6-14 - Roles and permissions in the Zope management interface.

The management screen in Fig 6-14 was accessed from the Security tab of the root object in my local Zope installation. The column headers at the top (Anonymous, Manager, Marketing, Owner) represent the roles defined on my system within the context of this object. The row headers along the left-hand side represent permissions. The checkboxes at the intersections of the columns and rows represent the assignment of a permission to a role in the context of (in this case) the Zope root folder. The act of assigning a permission to a role in the context of an object determines which roles (groups) can perform the action represented by the permission.

In Fig 6-14, the role Anonymous has the permission Access Contents Information, and not much else. The role Marketing has no permissions. The role Manager has all the permissions we can see in this screenshot (there are more that extend below those shown).

One of the results of this configuration is that users with the Manager role has the permission Add Documents, Images and Files. Let's concentrate on this for a moment. Permission names, by their nature, should describe an atomic action or a set of atomic actions. Our example seems to mean that a user possessing the Manager role can add documents, images, and files to the Zope on my hard disk. Pretty easy. Except... what does that mean, exactly? What kind of document? A DTML document? A PDF document? What kind of file? What does Add Documents, Images, and Files really mean? To be quite honest, I'm not really sure. The opaqueness of permission names is one of the problems with the current permissions implementation in Zope, and it may hopefully soon be remedied by the ability to attach comments to permissions, or maybe be augmented with some sort of introspection capability. But for now, it serves as a convenient jumping off point to describe a single permission in more detail, in which we (literally) will find out what Add Documents, Images, and Files really means to Zope.

Here's where it starts to get hard. Go grab a drink while I grep my hard drive.

I'm back. Having a role which allows you to Add Documents, Images, and Files means this: you may add Zope DTML Methods and DTML Documents, Zope Image objects, and Zope File objects to the container on which this intersection of permission/role information is defined. More explicitly, it means that you can utilize the following methods defined within the Zope core:

  • You may execute the method named addForm defined In the OFS package's DTMLMethod module
  • You may execute the method named addForm defined in the OFS package's DTMLDocument module
  • You may execute the method named manage_addImageForm defined in the OFS package's Image module
  • You may execute the method named manage_addFileForm defined in the OFS package's File module

Let's not sweat the gory details of what this means in terms of Python right now or how I found the meaning of this permission name (package and module are Python-specific terms which I've neglected thus far to define, and I don't want to go into a description of the Zope source tree right now). Instead, let's whine! The permission name lied to us. It told us that we were able to create documents, files, and images with its possession. It didn't mention anything about DTML Methods, even though it controls their creation as well. It was also vague, not telling us what types of "documents" it allowed us to add. As it turns out, it refers only to a DTML Document, but it would be almost impossible for us to infer that from its name in a site with many installed Products which define some sort of "document" object. This is all pretty sad, but besides letting us know we need to provide a facility for describing permissions within Zope, it gives us an opportunity to demonstrate the importance of naming permissions explicitly and descriptively. It also gives us the necessary excuse to describe the logic that underlies the "pretty name" that permissions let us show to our application users.

As shown in the Add Documents, Images, and Files explanation, a fact becomes evident: permissions are method groupings. When I state that "permissions are method groupings", what I mean is that a permission definition is made up of one or more methods that are defined on an object which you create during Product development. If a permission is associated with one of the methods of an object you create, it means that a user which possesses a role which has been granted that permission may execute that method. It also means that a user who does not possess a role which has this permission may not execute this method. In our example, four methods are represented by the Add Documents, Images, and Files permission: a constructor method for DTML Documents, a constructor method for DTML Methods, a constructor method for Images, and a constructor method for files. In our example, if I possess a role that has been assigned the "Add Documents, Images, and Files' in the context of the root folder, I may add DTML Methods, DTML Documents, Images, and Files to the Zope root folder. Since my user chrism has the Manager role in the context of the Zope root folder, I can do all these things. However, if the (strong, manly) chrism user did not possess the Manager role, I would not be able to add these things to the Zope root folder, because I could not make use of the methods which perform their creation, and it does not seem that any other roles possess the necessary permission which would allow me to do so.

Method protection and grouping is the underlying definition of a Zope permission, and it also clarifies a statement I made earlier having to do with a concept that might be familiar to Smalltalk programmers: you may think of permissions as "method categories" or "method groupings" which desribe a particular intent or set of intentions on the part of the user. For example, if in your Product, you define a permission named "Delete Underwear", you can then group the methods which make up the actions associated with deleting underwear into this permission. For example, if you've defined a DTML method or Python Method or base class method that actually performs the deletion of underwear named deleteUnderwear, you would probably want to assign the "Delete Underwear" permission to it within your Product's declarative security settings. Likewise, if you had another public method which was similar, such as deleteUnderwearConditionally (for whatever reason), you would probably also want to associate it with the "Delete Underwear" permission instead of making a new permission named "Delete Underwear Conditionally". Thus, one permission can be used to group and protect a set of methods.

User Defined Roles

Arbitrary roles may be added by Zope site managers in any object which exposes the Security tab in the Zope management interface by using the User Defined Roles form within the resulting management screen. Let's take a look:

Figure 6-15 - User-defined role add form.

What you're seeing in Fig 6-15 is the "bottom" of the management screen shown when you visit the "Security" tab in a "folderish" object like a Folder or a ZCatalog. You'll note that the "User defined roles" section has a textbox for adding a rolename and an "Add Role" button. Why is this useful? It provides site managers a way to create "special" roles easily without creating Products. If a site manager chooses to programatically or declaratively protect objects using roles beyond the definitions that are provided by Zope or by your Product, she may do so by creating a user defined role, associating permissions with the role on particular object instances, and associating users with the role she creates. She might do this in order to provide special site security that you did not anticipate when you developed your Product. User defined roles extend a site manager's ability to granularly secure object instances.

Adding a "user defined role" to a Folder makes this role available for use within objects defined in the the Folder the role was defined in and within all objects in subfolders of the Folder the role was defined in. Due to this, we can say that user defined roles "flow down" the folder hierarchy, however they never "flow up" the folder hierarchy. If you define a role in a subfolder, its parent folder will know nothing about it. However, its child folders will. The child folder will also know about all the roles defined in all of its parent folders. This is actually true of all product-defined roles, user-defined roles, and "local roles" (which we haven't yet discussed), but it's particularly recognizable when dealing with user defined roles.

It's wise not to get too carried away with user defined roles. Let's consider assigning user defined roles to users that have a "home" user folder that is "above" the folder in which the user defined role was created. Well... you can't. At least the role cannot be assigned the role via the Zope management interface. For example, if I create the user defined role "Gub" in the Marketing folder of my Zope site, I will not be able to assign the "Gub" role to the user chrism, because his "home" user folder is the user folder in the root of my Zope installation, which is "above" the folder in which the "Gub" role was defined. However, the jed user is perfectly capable of being assigned the "Gub" role, because his "home" user folder is inside the Marketing folder, where I defined the role.

To be honest, I don't really want to think about this too much. I'm a firm believer in making things as simple as possible. Here's the bottom line: though Zope makes almost everything possible, using Zope features expressly for the sake of their existence is just not worth it most of the time. To keep things simple, if you're not doing complicated delegation, and only if you need them, user defined roles are best created at the root folder of your Zope installation, where they're sure to "flow down" to all defined user folders. However, for complicated security setups with lots of delegation, the ability to create user defined roles in subfolders can be quite successfully combined with local roles, which we'll explore in the next section.

Local Roles

A previously defined role may be added to a single user's list of roles explictly in the context of a single object instance and its children using a "local role," also accessible through the Security management view. Local roles can be added to any object which exposes the Security tab in the Zope management interface. This is something new. We've figured out that roles are a way of grouping users. Local roles let you shortcut the limitations of declarative assignment of roles to users in the user's user folder. Using local roles, you can provide roles in addition to those granted to specific users on a per-user basis only in the context of the object on which they're defined as well as all of its subobjects. Let's take a look at a local roles assignment screen:

Figure 6-16 - Local roles management form in the Marketing folder.

I accessed this management screen by clicking on the "local roles" link contained within the explanation wording in the Security tab management interface screen of the Marketing folder. In the resulting screen shown in Fig 6-16, we can see a brief but accurate description of local roles in the form of explanatory text at the very top of the page, the concepts behind which I think we've already covered. We also see two forms.

The first form displays the local roles already granted to users in the context of the Marketing folder. We can see from this that the (stunning) chrism user has already been granted the Owner local role for this object. We'll talk a little bit later about what the Owner role represents, but for now it should suffice to say that this local role was granted to the chrism user on the Marketing folder as a result of the chrism user being its creator. If I wanted to, I could revoke this local role by selecting the checkbox next to chrism and clicking "Remove". I don't, so I won't.

The second form shows us a list of users and a list of roles. The idea here is that you may select a single user to which you can grant any or all of the roles shown in the "Roles" box. It's interesting to note here that the list of users is a composite of the users defined in the root user folder and the users defined in the Marketing user folder (ie. chrism is defined in the root user folder, and jed is defined in the Marketing user folder). It's also interesting to note that the roles presented for selection are a composite of several system roles (Manager, Owner) and several user defined roles (Marketing, clambake, and gub). Furthermore, it's interesting to note that the user defined roles that show up in the role box are a composite of user defined roles which were defined on the root Zope folder (Marketing) and two others which were defined on the Marketing folder (clambake and gub). You didn't see me add the clambake user defined role because you were busy, but I added it on the root folder a while ago.

The selection of a user in the "User" box combined with the selection of roles in the "Roles" box and the submission of the form will assign a set of local roles to the user within the Marketing folder on which it is defined. For instance, I will choose to provide the jed user the clambake and gub local roles.

Figure 6-17 - Assiging big Jed the clambake and gub local roles.

Now Jed will have the clambake and gub roles within the Marketing folder and within any folderish objects which get defined beneath the Marketing folder. What does this buy us? To be honest, I haven't really been paying attention, but let's find out. We'll find out what roles Jed has been given in his user definition.

Figure 6-18 - Figuring out what roles Jed has in his user definition.

Well, it seems that Jed has been granted the Manager and Marketing roles within the acl_user folder his user is defined in (which is in the Marketing folder). With the addition of these local roles, Jed will have these effective roles when he visits anything in the Marketing folder and below: Manager, Marketing, clambake, gub. Jed's 'acl_user'-defined roles combine with the local roles assigned within the Marketing folder object when he visits stuff inside there. We've not given Jed the clambake and gub roles everywhere, just within the Marketing folder. If we had given Jed these local roles inside the context of a different object --for example, inside a DTML Method-- he would only obtain the clambake and gub roles (plus the ones that have already been granted to him in his user definition) when he visited that object, and since a DTML method has no children objects, he would have those roles in no other context unless we explicitly assigned them to him.

Remember a while back when I said that user-defined roles created on subfolders were a candidate for combination with local roles? Though I'm not going to demonstrate it, it's a fact that I can't assign the chrism user the gub role within his user definition inside the user folder in which he is defined. It just won't show up in that view, because 'chrism''s user folder knows nothing about it. But I can provide him with the gub role via the local roles facility on the Marketing folder if I so chose. It would be kind of silly, because he's already got the Manager role at the root, so the local roles would not be very helpful (the gub role wouldn't provide him any additonal access), but nonetheless, I could do it if I wanted to. I don't, of course. This trick is more useful if you've got otherwise unprivileged users defined at a level "above" where you've defined a user defined role, such as in the case that you're doing a lot of delegation mixed in with complicated security policies. You can use local roles to get around not being able to grant users roles within their user folder definition by adding contextual roles to their user-folder defined ones. Using this feature requires too much thought for my taste, but it's there if you need it.

Local roles should be used sparingly. If all of this explanation of local roles is gibberish to you, well, don't worry about it. There will come a time during Product development that you'll run across a hard security problem that can be solved via their use. Read this section again at that time. Until then, forget everything I just said.

Proxy Roles

An "executable" object can make use of a "proxy role" definition, which replaces the roles of the executing user when run. This feature may be used to expand or limit access to resources when an object is executed. Proxy roles are different animals than local roles. Local roles add roles to a specific user in the context of an object, while proxy roles replace roles of the executing user in the context of an object. They are defined by visiting the Proxy tab in the management interface of any executable object. Executable objects include DTML Methods, DTML Documents, SQL Methods, External Methods, Python Methods, and Perl Methods, so the management interface of these kinds of objects is where you'll see the Proxy tab. Let's take a look at the Proxy tab of an existing DTML Method:

Figure 6-19 - Proxy roles dialog of the user_list DTML Method.

In Fig 6-19, we have the option of defining proxy roles which grant any level of access between "make no change" (select none of the roles in the proxy roles box and click "Change") and "all roles" (select Anonymous, Manager, Marketing, Owner, and clambake and click "Change"). Or anything in between (for example, just Owner).

When we select a single role or a set of roles from the dialog and click "Change," we're defining the effective roles of any executing user at execution time as those roles which we choose in the dialog. This can be used to extend access (for example, choosing a Manager proxy role would allow this DTML method to do manager-level things even if an executing user did not have the Manager role assigned to him via a local role or user folder-defined role. This can also be used to limit access. For example, choosing the Anonymous proxy role would limit this DTML method to executing with the Anonymous role even if the executing user has high privilege. This behavior is useful because it allows us to change the privilege level of an executing user in the context of a single object's execution. I might want to set up a proxy role on a DTML method which creates an object which, under normal circumstances, the executing user would not be permitted to create.

On a per-object basis, proxy roles can only be assigned to potential executors out of the pool of effective roles possessed by the object's owner. What this means is that the selectable proxy roles shown in the Proxy screen will always be limited to the effective roles possessed by the owner of the object. We haven't talked about ownership much yet, but it's coming up soon. Suffice to say that this feature allows safe delegation of the ability to create proxy roles in an environment with complicated security.

If you don't know why you would need proxy roles, chances are you don't. Don't make life hard on yourself, and keep it simple by not trying to use them until you run across a situation where you definitely think you might need them.

Roles Defined by Products

Roles may additionally be defined by Product authors as a part of their Product definition code. Regardless of whether a role is created manually by a Zope site manager or automatically by a Product author as a result of a site manager installing the Product which creates an additional role or set of roles, the resulting roles are treated identically by Zope.

Permissions Defined by Products

Permissions may only be defined by Products and Zope core code. Certain incantations within your Zope code add Product-specific permission definitions to the Zope instance in which your Product is installed.

Ownership

Because Zope is completely web-manageable, and because it has a sometimes mind-boggling assortment of security features, it allows you to do some astoundingly cool things quite safely. For instance, you can let arbitrary user run DTML, Python, or Perl code on your Zope-powered webserver with a fair level of assurance that those users will not be able to gain access to protected areas of your site or otherwise deface it. I personally really get a kick out of this. An example of this feature in action is the Zope.org site, which allows anyone to sign up and script DTML (and possibly by the time you read this, Python and Perl) in their own member space to their heart's content.

We call this ability "safe scripting." Because DTML, Python, and Perl code created by anonymous or pseudo-anonymous Web users in Zope is "safety checked" before it executes, providing this level of access this isn't complete Internet security suicide, as it would be if you for instance gave random visitors the ability to construct and execute scripts on your server via a web interface in raw Perl via CGI. Safe scripting, in simple terms, assures that people can't do bad things on your website while scripting, while still extending them the power to do useful things via presentation and scripting languages such as DTML, Python, and Perl. It's also a piece of the puzzle that lets you delegate content management power on a Zope-powered website. You need not do anything special to "turn this feature on", it's just... there. If users have the appropriate permissions, they can add executables to your Zope site.

As I've stated a number of times in this guide, using Zope features solely because they exist is not a smart idea. If you don't need to extend users the ability to enter script code into your website.. don't do it. But in case that it's appealing to be able to allow untrusted or semitrusted folks to contribute script content to your website, by all means, feel free to give users access to do so, because they really can't do too much damage. The concept in Zope of ownership is one facility that makes this a reality.

When a user creates content in Zope, she automatically becomes the Owner of that content. For example, if I create a DTML Method with an id of create_management_user in the root folder of my local Zope installation like so:

    <dtml-with acl_users>
      <dtml-call expr="REQUEST.set('name', 'fudgeguy')">
      <dtml-call expr="REQUEST.set('password', 'fudge')">
      <dtml-call expr="REQUEST.set('confirm', 'fudge')">
      <dtml-call expr="REQUEST.set('roles', ['Manager'])">
      <dtml-call expr="REQUEST.set('domains', '')">

      <dtml-call expr="manage_users('Add', REQUEST)">

    </dtml-with>
    Success!

Can you guess what it does? When a trusted user runs it, it adds a fudgeguy user and in doing so, provides him with the Manager role within the top-level user folder. Holy hat! That's a pretty powerful little DTML method. But if just any old user could construct this method and run it successfully we'd be in trouble, for obvious reasons. If Joe Bloggs could make this DTML method work successfully, he could then log in as this user and would at this point probably have complete control of your Zope site. Not good.

Don't worry about it, because it just can't happen unless you really mess up by giving Joe the Manager users permission. If we added joe to our root acl_users folder, and we gave the clambake method all permissions except Manage users, and we subsequently granted the joe user the clambake role, Joe will be able to add DTML into our site, he'll be able to view the management screens, he'll be able to poke around in everybody's stuff, he'll be able to create database connections, and so on. He'll be able to just about anything. But when he tried to run the DTML that composes the create_management_user method, he'd see this:

Figure 6-20 - joe trying to view the create_management_user DTML method

Denied! In Fig 6-20 we see the power of roles and permission in action. A user can only access methods contained in permission assigned to a role which he has been granted. Since Joe has the clambake role in the context of the root folder, and the clambake role hasn't been granted the Manage users permission, Joe cannot access the manage_users method of the acl_users folder. This is old hat. We've been over this before.

Things however get a little trickier when we add the concept of ownership into the mix.

Since Joe has the capability of adding DTML to my site, he could easily construct a DTML method with the body of what I put in the create_management_user method. Though he could not successfully execute the code himself, he could get tricky. If he lured the unsuspecting (but still devilishly handsome) chrism management user into executing a method he created that has this body, without ownership playing a part it would be possible for Joe to trick chrism into executing the method by just visiting its URL. Maybe he would post a news item on my site that said "Cool Stuff At This Link, Click Here!!" at which point I would be absolutely compelled to click a URL which led to the create_management_user method while I was logged in as the chrism management user. Without the concept of ownership, the DTML method would happily execute, and the fudgeguy user would be added to the root user folder. Joe could then could log in as the fudgeguy user and have free reign of the site by virtue of fudgeguy possessing the Manager role.

Well, luckily, this can't happen either, thanks to ownership. "Executable" objects (DTML Methods, DTML Documents, SQL Methods, Python Methods, Perl Methods, and External Methods) execute with effective permissions equal to the intersection of the permissions of the object's owner and the executing user. When Joe creates his version of the create_management_user DTML method (he's cleverly named it get_me_some_manager_access), Joe becomes that method's owner. Whenever the get_me_some_manager_access method is executed, it will execute with the effective intersection of permissions between Joe's user (as the owner) and the executing user. This means in our specific example that access will be granted to the manage_users method of the acl_users object during the execution of the method only if both the executing user and Joe have the appropriate permission to be able to execute it. In reality, this means that the method can never be successfully executed by anyone, due to Joe's lack of the Manage users permission. If chrism tries to execute Joe's trojan get_me_some_manager_access method, the system will reject the request because the intersection of permissions between chrism and joe does not allow access to the manage_users method. The rejection of the request will cause an "Unauthorized" exception to be raised by Zope, and the transaction which encapsulates the request will not be completed, thus no harm is done.

I know this is compilcated. Don't let all this scare you, it's complicated, but it's mostly complicated only because it's highly dynamic. You also needn't worry much about it unless you intend to allow untrusted or semitrusted users to add executable Zope content to your site. It's convenient to think of executables on a Zope site as equivalent to executables in UNIX, they're just much easier to run. In UNIX, you need to type "rm -rf " or something similar as a privileged user to do real damage, or at least you need to type in the name of a shell script which contains an equivalent command. Executing programs in Zope is much easier. You run executables by viewing them, which usually consists of single-clicking on a link. Because they're so easy to run, and because web browsers allow redirection without the consent of the operator in which HTML can direct your browser to a URL without your knowledge, the restrictions imposed on executable content via ownership in Zope are a highly Good Thing. If these restrictions didn't exist, it would be possible for someone to construct a page internal to your site which did bad stuff* when you clicked on them or were redirected to them.

*NOTE: The reason we know all about this problem is because Zope version prior to version 2.2 do not have the concept of ownership. All the bad things that ownership protects you against can easily happen to you under Zope versions prior to 2.2. It's highly recommended that if you're running a version of Zope prior to 2.2 that you upgrade.*

It's also beneficial to note here that your browser can make you vulnerable to security breaches within Zope whether you're running Zope 2.2 or an earlier version. Whatever version of Zope you're using, these are good rules to follow (with or without the ownership concept):

  • Do not view untrusted content while logged on with privileges that could be abused and always end your session with those privileges as soon as your work is done.
  • Create temporary users with "just enough privileges" to do a particular task and perfom as much work as possible with these restricted privilege users.
  • Turn off JavaScript in your browser while administering the site! People can do tricky things with JavaScript.
  • Keep an eye out for suspicious activity - looking at the "undo" tab on the top-level folder of your site is a good way to get an idea of who has been doing what.
  • Keep good backups and if possible avoid packing your site - that will give you the most options for undoing site changes if you need to.

Other important things about ownership:

  • The superuser cannot own anything. This is what makes it crippled. This is on purpose.
  • An executable does not store direct a reference to its owner. Rather, a tuple consisting of the physical path to an owner's user folder and the owner id will be used to look up the owner.
  • Ownership is mostly independent of the "Owner" local role. The "Owner" local role concept predates Zope 2.2, and for backwards compatibility purposes, its been necessary to keep it, despite its redundance in the face of real ownership. Steps have been taken to make this a little more palpable, however. Whenever an owner is changed, the new owner will get the owner local role on the object. The one exception to this rule is that the Anonymous User user never gets the owner role.
  • Only one user can be the owner of an object, while many users can have the owner local role on an object. Owner local roles can be changed without changing the owner.
  • If the owner of an object is deleted, the object becomes owned by the special nobody user.
  • Owners can be changed. Users possessing the "Take ownership" permission will be able to take ownership of objects via the object's "Ownership" tab of the Zope management interface. Note that ownership may not be given away, but only taken explicitly.
  • Ownership is not effected by editing an object. An object's ownership does not change as a result of editing it or changing it.
  • Ownership of an object is assigned to the copying or importing user when an object is copied or imported.
    • Objects created by Zope itself (such as objects created during Product initialization) are created without an owner. Objects in the Control Panel are never owned. There is a special Zope user named system that performs any operations done during startup. The system user is allowed to create objects and the objects created by system are either unowned or acquire their ownership from the place they are added.
    • If an unowned executable is called directly or indirectly from another executable, then the executable can access resources as long as the person running the executable can.
    • It is not possible to contrive to make an object unowned outside of the Control Panel without having Manager privileges.

Warts and Gotchas

What now comprises Zope has been around now in various forms since about 1995. It's now the year 2000. The period in between then and now is equivalent to six hundred million Internet years (according to my estimations). Over that period of time, Zope has collected its fair share of warts that are difficult to remove for backward-compatibility reasons. You'll undoubtedly be bitten by some of these, so this is an important section. In later versions of Zope, it's likely that they will be phased out and ultimately removed.

manage_* Methods

Python Product methods whose names begin with the string "manage_" ("manage-under" methods) are only accessible to users who possess the "Manager" role, no matter what permissions you set on them. I dont know the history behind this, so I can't comment on it further. This does not hold true for methods of ZClass-based Products.

Python Base Class Methods Without A Docstring

Methods in Python base classes or within Python-based Product classes may have "docstrings". This is a feature of the Python language. An example:

    def mymethod(self):
        """ This is a docstring """
        pass

This method (named mymethod) has a docstring with the content "This is a docstring." Zope treats Python base class and Python Product methods with docstrings "specially." If a Python base class or Product method contains a docstring, it indicates to the Zope publishing machinery that this method is "web publishable", which means it may be called directly from the web. It means that someone can type the method's URL into a browser and be presented with the results of the method. If it does not have a docstring, however, it may not be accessed directly through the web. Methods without docstrings when called from a URL will cause the publishing machinery to return a "Not Found" error. These methods, however, may be called via DTML or other scripting facilities on the same Zope site. This is a "hack" that will most likely be replaced by a permissions-based restriction in future Zope versions.

Methods And Attributes Which Begin With An Underscore Character

Methods and attributes with a leading underscore such as "_myattribute" or "_mymethod()" defined in Python Products may not be accessed via DTML or other through-the-web scripting facilities.

Python Product Security ("Disk-Based")

Finally, we've reached a point where we can start applying all the security knowledge we've accumulated in the sections above. Zope allows you to create a Product in two ways: via a "Python Product", which is a collection of Python modules and DTML files on your OS' filesystem, and via a "Through The Web Product", which is a collection of ZClasses. This first section deals with the security of "disk-based" Python Products.

A Simple Python Security Demonstration Product (PythonForum)

A simple ("disk-based") Python Product named PythonForum has been developed with the intention of showing the Zope security machinery "in action". It's a small Zope Product written entirely in Python that shows delegation, role manipulation, programmatic security, and other security features. It is available on the Zope.org site a few clicks away from the URL http://www.zope.org/Members/mcdonc. Some of the upcoming sections will refer to this Python Product as a reference point. It's probably a good idea to install it, or at to at least unpack it and examine the source code.

Download and install the PythonForum Product by expanding it into your "top-level" Zope folder (if you're on Windows, use WinZip to expand it). A directory named "PythonForum" should show up in your lib/python/Products directory. Then restart your Zope, and instantiate a PythonForum as a user with "Manager" privileges, and play around with it a little bit. While you're playing around with the product itself, browse the source code. There are lots of comments in the source code that try to explain what is going on at each step. PLEASE NOTE THAT THIS PRODUCT WORKS ONLY WITH ZOPE 2.2.1 and GREATER! If you have an earlier version of Zope, either upgrade it or install the PythonForum product on a independent copy of Zope 2.2.1 or greater.

For your convenience, colorized Python source code files (and non-colorized DTML source files) from the PythonForum Product are available online. They are referenced lightly in the succeeding sections.

The Zope Python Product Initialization Process

Zope permits access to resources deterministically. When you code your Product in Python, certain declarative assertions made by you within your Product code tip Zope's security machinery off to what your intentions are for allowing or denying object access. These declarative security assertions are gathered for the most part during Product initialization. Production initialization happens in two places:

  • when your Product's modules are imported.
  • when your Product's __init__.py module's initialize() function is called.

The initialization process serves the purpose of processing the declarative security assertions made within a Product's code.

Python Product Declarative Security Assertions

Declarative security assertions are part of class and attribute definitions made by your Product. It's important to know that in the below descriptions, the word "attribute" can mean either a Python method (as defined by a "def" statement) or a Python attribute (as defined via assignment). There are four major assertions to be made as part of Zope class and attribute definition:

  • __roles__ - a class attribute or an attribute of an attribute which explicitly defines the roles required to access the protected item when an instance of the class or an attribute is called by through-the-web code. Explicitly setting roles for instances and attributes is not frequently necessary.
  • __class_init__ - a method of Zope classes called to initialize security settings for Zope when the class is imported and processed by the initialization machinery. This almost always delegates to the function Globals.__default_class_init__. This method must be defined on all classes that do not inherit from the Zope Persistent class for the class' security declarations to be processed.
  • __ac_permissions__ - a class attribute which is a tuple representing the permissions necessary to access attributes of the class when an instance of the class is called by through-the-web code. This is the major workhorse in declarative security.
  • __allow_access_to_unprotected_subobjects__ - a class attribute which, when true, allows subobjects of an instance that are not protected by a definition in the instance's __ac_permissions__ to be accessed by through-the-web code. When this attribute is false, class and instance attributes of the class which are not listed in __ac_permissions__ cannot be accessed by through-the-web code. When this attribute does not exist, the default is to not allow access to unprotected subobjects (e.g. the same as __allow_access_to_unprotected_subobjects__=0). Setting this attribute to "true" reduces the potential "secureness" of your Product. It's wise to set this attribute to false and declare all the attributes you want to be accessible within __ac_permissions__.
The Cumulative Nature of Python Product Security Declarations

Security declarations are generally made as a part of a class definition within Zope. Because Python classes support multiple inheritance, it's possible that a class may inherit from other classes which themselves declare security assertions. It's important to know that you only need to protect attributes and methods of your class with declarative security assertions. You need not bother trying to protect methods and attributes inherited by your class via its base classes unless you wish to explicitly redefine their existing security assertions. These class' security assertions are processed independently from your derived class during class security initialization.

The __roles__ Mechanism

Python, the language in which Zope is written, has no built-in security features. Thus, as Zope evolved, the security features necessary for web collaboration were implemented on top of Python, but at a very low level. Zope more or less extends Python by implementing libraries and initialization routines to meet the security requirements of a collaborative application server.

The "lowest" level of this security is implemented by Zope's parsing of Python Product classes for "role" declarations. When a Python product is initialized, if the proper directives are provided within the code of the classes defined, Zope will protect attributes and methods defined within the classes.

Let's take a look at part of the Comment class of the PythonForum Product. Particularly, let's look at the explicit __roles__ declarations made within the class, paired with the attributes they protect:

    mypublicattribute = "public"
    mypublicattribute__roles__ = None

    myprivateattribute = "private"
    myprivateattribute__roles__ = ()

    mysemiprivateattribute = "semiprivate"
    mysemiprivateattribute__roles__ = ('Manager',)

  The Importance of '__class_init__' To The Class Initialization Process

   The "flag" that triggers Zope's security-declaration-initialization
   process is a class attribute named '__class_init__'.  Classes which
   inherit from the Zope 'Persistent' class have this attribute
   predefined for them.  *Classes which do not inherit from Persistent
   need to declare it explicitly*.  Declaring this attribute is almost
   always the same for any class that does not inherit from the
   Persistent class.  Here's the common idiom, by example::

    import Globals
    class Foo:
        """ The foo class """
        # initialize the security declarations of the class
        __class_init__ = Globals.__default_class_init__

   Don't think too hard about what this means.  Just know that if you
   have a class which does not inherit from Persistent that has
   methods or attributes that need to be accessed from DTML or other
   through-the-web code, you must define a '__class_init__' for it if
   its security declarations are important to you.  You may also see
   this common idiom in Zope code at the module level::

    Globals.__default_class_init__(MyClass)

   This performs exactly the same operation as defining a
   '__class_init__ = Globals.__default_class_init__' class attribute
   in your Zope class, it's just a different way of "spelling" it.
   This the way the PythonForum Product initializes security on its
   classes (see Forum.py).

   When in doubt, and you're not sure whether your Zope Product class
   needs a '__class_init__', go ahead and define it as '__class_init__
   = Globals.__default_class_init__' (make sure to 'import Globals'
   first).  Adding this "incantation" to a class that doesn't need it
   won't hurt anything, and it may save you some debugging time later.

The Python Product Security Workhorse (__ac_permissions)

__ac_permissions__ = ( (View, [getBody,getTitle,getUserName,getCreationDate,view, 'index_html'], [Forum Participant,'Manager']), )

A ZClass Product

No ZClass product has yet been developed, I'm afraid. Hopefully, this will be "soon to come" (once I have a few days).

The Zope Security Policy

The Zope Security Policy is the primary Zope security policy implementation, although it may (infrequently) be replaced by user-defined policies. This policy is used when validating access to objects from DTML and other through-the-web code (such as PythonMethods and PerlMethods). It is not honored when disk-based Python code is the caller. Here is a summary of the policies implemented by the Zope standard security policy on through-the-web code:

  • When performing a "named" access, if the name requested starts with aq_, then the name must be aq_parent or aq_explicit.
  • If the value being retrieved doesn't have roles:
    • If the value was acquired, then access is denied.
    • If the value was not acquired:

      The container is checked for a security assertion that defines how access should be granted to unprotected subobjects. The container can have a security assertion in two ways:

      • The container can have an attribute __allow_access_to_unprotected_subobjects__, which has the assertion as a value.
      • An item can be added to the dictionary at AccessControl.SimpleObjectPolicies.ContainerAssertions with the container type as a key and the assertion as a value. This mechanism is used to make assertions for Python extension types like standard Python types, such as lists and dictionaries.

      An assertion can take 4 forms:

      • It can be None, in which case, access is denied to all unprotected subobjects.
      • It can be a Python integer, in which case, access is denied to all unprotected subobjects if the value is 0 and may be allowed otherwise.
      • It can be a Python dictionary, in which case access may be allowed if the dictionary has a true value for the name used, if the access was a named access, or for None, if the access was not a named access. If the dictionary doesn't have a value or has a false value, then access is denied.
      • It can be a callable object, in which case, it will be called with the named used for access or None, if the access was not a named access, and the value accessed. Access may be allowed if the value returned from calling the assertion is true and denied otherwise.

      If the container doesn't have a __roles__ attribute, directly or through acquisition, then access is allowed, assuming that there was an appropriate unprotected sub-object assertion.

      If the container has a __roles__ attribute, directly or through acquisition, then the roles value is used as if it was found in the accessed value. Assuming that there was an appropriate unprotected sub-object assertion, then the roles will be checked as described below.

  • If the value's roles (including roles gotten via the value's container) is None or contains Anonymous, then access is allowed.
  • If the authenticated user has any of the value's roles (including roles gotten via the value's container), then access is allowed.
  • If an Executable Object is being run and the Executable Object has a proxy_roles attribute, then access is provided if the proxy roles overlap the accessed value's roles (including roles gotten via the value's container).
  • If none of the above three conditions apply, then access is denied.

Programmatic Security

Zope provides a set of related APIs for programatically manipulating and checking security during code runtime. These APIs exist:

  • Security Policy
  • Security Management
  • Security Manager
  • DTML Security API
  • User Object API

Together, they form a convenient mechanism for validating user object access while you're coding in DTML or Python.

Security Policies

Security Policies are generally used to determine whether access should be granted to an object. The Security Policy used within Zope is almost always the default Zope Security Policy (discussed in the previous section). Security Managers (yet to be discussed) delegate access checking to Security Policies. Security Policies are objects which implement the following methods:

validate(accessed, container, name, value, context)
Validate access .

Arguments:

accessed
the object that was being accessed
container
the object the value was found in
name
The name used to access the value
value
The value returned by the access
context
A Security Context, which provides access to information such as the context stack and AUTHENTICATED_USER.

Example:

       None.  You generally need not call this unless you're coding
       for the Zope core.

checkPermission(permission, object, context)
Check whether the security context allows the given permission on the given object.

Arguments:

permission
A permission name
object
The object being accessed according to the permission
context
A SecurityContext, which provides access to information shuch as the context stack and AUTHENTICATED_USER.

Example:

       None.  You generally need not call this unless you're coding
       for the Zope core.

Security Management

The security management interface exists to provide access to Security Managers and to provide a way to change the system Security Policy. The interface is implemented by the AccessControl package (in lib/python/AccessControl).

getSecurityManager()
Get a SecurityManager object.

Example:

       import AccessControl
       security = AccessControl.getSecurityManager()

setSecurityPolicy(a SecurityPolicy)
Set the system default security policy. This method should only be caused by system startup code. It should never, for example, be called during a web request.

Example:

       None.  You probably never want to replace the default security
       policy.

Security Manager

A security manager provides methods for checking access and managing executable context and policies. It is used in the Zope core extensively. Most Product authors will use the checkPermissions and getUser methods it defines, but will probably not be all that interested in the other methods it defines, as they are generally handled "magically" by the Zope Security Policy. A Security Manager object has the following methods:

validate(accessed, container, name, value)
Validate access.

Arguments:

accessed
the acquisition-wrapped object in which the value object was accessed.
container
the actual container object in which the value object was found.
name
The attribute name used to access the value object.
value
The value object retrieved though the access (the object itself).

The arguments may be provided as keyword arguments. Some of these arguments may be ommitted, however, the policy may reject access in some cases when arguments are omitted. It is best to provide all the values possible.

A boolean value is returned indicating whether the value is accessible. An Unauthorized exception may be raised in some cases.

This method is useful to check security assertions within the context of other objects. For example, an object may be accessible when accessed in the context of a different object, but may not be accessible when accessed directly.

Example:

       import AccessControl
       security = AccessControl.getSecurityManager()
       security.validate(Portal.Works,Portal, 'Foo', Foo)

       This example provides the case of an object accessed from DTML
       named "foo".  In a DTML method that is called via
       /Portal/Works/foo, the "foo" object is *accessed* via
       acquisition (the first argument).  But it actually "lives" in
       the "Portal" *container* (the second argument).  Its *name* is
       the string 'Foo' (the third argument).  Its has the *value* of
       the Foo object (the fourth argument).  The call to
       geSecurityManager.validate() performed on it would return a
       boolean value indicating whether this object can be accessed in
       the context of its container and acquisition path, and as
       determined by the effective Security Policy and aggregated
       permissions associated with the object.  You probably never
       need to call this during Product coding, but it's available.

validateValue(value)
Validate access. This is a shortcut for the common case of validating a value without providing access information.

A boolean value is returned indicating whether the value is accessible. An Unauthorized exception may be raised in some cases.

Example:

       import AccessControl
       AccessControl.getSecurityManager().validateValue(Foo)

       This example provides the case of an object value validated
       outside the context of any container or acquisition context.
       You probably won't need to call this during Product coding.

checkPermission(permission, object)
Check whether the security context allows the given permission on the given object. Return a boolean value.

Arguments:

permission
A permission name
object
The object being accessed according to the permission

Example:

       import AccessControl
       AccessControl.getSecurityManager().checkPermission('Add Folders', Foo)

       Checks whether the user executing the method has the 'Add
       Folders' permission in the context of the 'Foo' object.  This
       is useful during Product coding.

addContext(anExecutableObject)
Add an ExecutableObject to the current security context.

There is no return.

Example:

      None.  You probably never need to call this during Product coding.

removeContext(anExecutableObject)
Remove an ExecutableObject.

There is no return.

getUser()
Return the authenticated user.

Example:

      import AccessControl
      AccessControl.getSecurityManager().getUser()

      This is equivalent to something like REQUEST['AUTHENTICATED_USER']
      but is a bit cleaner, especially if 'REQUEST' isn't handy.

calledByExecutable()
Determine if called through an ExecutableObject.

A true value is returned if any ExecutableObjects have (directly or indirectly) called the current method and a true value otherwise.

This can be used to determine if an object was called (more or less) directly from a URL, or if it was called by through-the-web provided code.

Example:

      None.  You probably never need to call this during Product coding.

DTML Security API

The DTML Security API an interface implemented by the DTML namespace (aka _) that provides tools for checking access. It provides the following methods:

SecurityValidate(accessed, container, name, value)
Validate access.

Arguments:

accessed
the object that was being accessed
container
the object the value was found in
name
The name used to access the value
value
The value retrieved though the access.

The arguments may be provided as keyword arguments. Some of these arguments may be ommitted, however, the policy may reject access in some cases when arguments are ommitted. It is best to provide all the values possible.

Example:

      None.  You probably won't need to call this during DTML coding.

SecurityValidateValue(value)
Convenience for common case of simple value validation.

Example:

      None.  You probably won't need to call this during DTML coding.

SecurityCheckPermission(permission, object)
Check whether the security context allows the given permission on the given object.

Arguments:

permission
A permission name
object
The object being accessed according to the permission

Example:

      To check whether it's possible to add a folder to folder ACME:

      <dtml-if "_.SecurityCheckPermission('Add Folders', ACME)">    

SecurityGetUser()
Get the current authenticated user

Example:

      To print the current username:

      <dtml-var "_.SecurityGetUser().getUserName()">

      This is equivalent to something like REQUEST['AUTHENTICATED_USER']
      but is a bit cleaner, especially if 'REQUEST' isn't handy.

SecurityCalledByExecutable()
Return a boolean value indicating if this context was called by an executable

Example:

      None.  You probably won't need to call this during DTML coding.

The end. Sorry for the abruptness of this ending. I had hoped to wrap things up a little more cleanly, and to give a blow-by-blow narrative of the PythonForum product and a ZClass product, but in the interest of time, I've decided to release this document "as is". Please send comments to [email protected].

This document is part of a continuing effort to devise a "Zope Developer's Guide". If you're interested in contributing, or if you have comments, please drop me a line.

  • General
    • The Superuser
    • User Folders
    • Authentication Credentials
    • Users
    • Roles and Permissions
      • Roles as "actors"
      • Permissions as "method categories"
      • User-defined Roles
      • Local Roles
      • Proxy Roles
      • Roles Defined By Products
      • Permissions Defined By Products
    • Ownership (jim's server-side trojan docs)
    • Warts and Gotchas
      • manage_* methods
      • methods that start with an underscore
      • Python base class methods without a docstring
  • Programmatic Security (interfaces wiki SecurityPolicies)
    • SecurityManagement
    • SecurityPolicy
    • SecurityManager
    • DTMLSecurityAPI
  • In ZClass-based products
    • Permission objects
    • Permission mappings
  • In Python-based products
    • Securing your product methods
      • __ac_permissions__
      • __class_init__
      • NO __ac_roles__
      • __roles__
      • NO __call__.__roles__ (BaseRequest.py)
      • NO __allow_groups__
      • Globals.default_class_init__(ob) and the role of the Persistent class
      • __allow_unprotected_access_to_subobjects__
      • the role of the "RoleManager" class
      • how ExtensionClass behavior facilitates security (extension classes can have attributes on methods, which define security)