5.2. PermissionsThe basic entity that the access controller operates on is a permission object--an instance of the Permission class (java.security.Permission). The Permission class itself is an abstract class that represents a particular operation. The nomenclature here is a little misleading, because a permission object can reflect two things. When it is associated with a class (through a code source and a protection domain), a permission object represents an actual permission that has been granted to that class. Otherwise, a permission object allows us to ask if we have a specific permission. For example, if we construct a permission object that represents access to a file, possession of that object does not mean that we have permission to access the file. Rather, possession of the object allows us to ask if we have permission to access the file. An instance of the Permission class represents one specific permission. A set of permissions--e.g., all the permissions that are given to classes signed by a particular individual--is represented by an instance of the Permissions class (java.security.Permissions). As developers and administrators, we'll make extensive use of these classes, so we'll need to investigate them in depth. 5.2.1. The Permission ClassPermissions have three properties:
Permissions can serve two roles. They allow the Java API to negotiate access to several resources (files, sockets, and so on). Those permissions are defined by convention within the Java API, and their naming conventions are wholly within the domain of the Java API itself. Hence, you can create an object that represents permission to read a particular file, but you cannot create an object that represents permission to copy a particular file, since the copy action is not known within the file permission class. On the other hand, you can create arbitrary permissions for use within your own programs and completely define both the names of those permissions as well as the actions (if any) that should apply. If you are writing a payroll program, for example, you could create your own permission class that uses the convention that the name of the permission is the employee upon whose payroll information you want to act; you could use the convention that the permissible actions on the payroll permission are view and update. Then you can use that permission in conjunction with the access controller to allow employees to view their own payroll data and to allow managers to change the payroll data for their employees. We'll look at both of these cases, starting with the classes that are provided within the Java API itself. These classes are used by the Java API (and in particular, by the security manager) to protect access to certain resources in ways that are fairly intuitive, given our knowledge of the security manager (but we'll examine that interaction in detail later). 5.2.2. Permissions of the Java APIThere are 11 standard permissions in the Java API, each of which is implemented as a class:
5.2.3. Using the Permission ClassWe'll now look into the classes upon which all these permissions are based: the Permission class. This class abstracts the notion of a permission and a name. From a programmatic standpoint, the Permission class is really used only to create your own types of permissions. It has some interesting methods, but the operations that are implemented on a permission object are not generally used in code that we write--they are used instead by the access controller. Hence, we'll examine this class primarily with an eye towards understanding how it can be used to implement our own permissions. Permission is an abstract class that contains these public methods:
Implementing your own permission means providing a class with concrete implementations of these abstract methods. Note that the notions of wildcard matching and actions are not generally present in this class--if you want your class to support either of these features, you're responsible for implementing all of the necessary logic to do so (although the BasicPermission class that we'll look at next can help us with that). Say that you are implementing a program to administer payroll information. You'll want to create permissions to allow users to view their payment history. You'll also want to allow the HR department to update the pay rate for employees. So we'll need to implement a permission class to encapsulate all of that: Class Definitionpublic class XYZPayrollPermission extends Permission { protected int mask; static private int VIEW = 0x01; static private int UPDATE = 0x02; public XYZPayrollPermission(String name) { this(name, "view"); } public XYZPayrollPermission(String name, String action) { super(name); parse(action); } private void parse(String action) { StringTokenizer st = new StringTokenizer(action, ",\t "); mask = 0; while (st.hasMoreTokens()) { String tok = st.nextToken(); if (tok.equals("view")) mask |= VIEW; else if (tok.equals("update")) mask |= UPDATE; else throw new IllegalArgumentException( "Unknown action " + tok); } } public boolean implies(Permission permission) { if (!(permission instanceof XYZPayrollPermission)) return false; XYZPayrollPermission p = (XYZPayrollPermission) permission; String name = getName(); if (!name.equals("*") && !name.equals(p.getName())) return false; if ((mask & p.mask) != p.mask) return false; return true; } public boolean equals(Object o) { if (!(o instanceof XYZPayrollPermission)) return false; XYZPayrollPermission p = (XYZPayrollPermission) o; return ((p.getName().equals(getName())) && (p.mask == mask)); } public int hashCode() { return getName().hashCode() ^ mask; } public String getActions() { if (mask == 0) return ""; else if (mask == VIEW) return "view"; else if (mask == UPDATE) return "update"; else if (mask == (VIEW | UPDATE)) return "view, update"; else throw new IllegalArgumentException("Unknown mask"); } public PermissionCollection newPermissionsCollection() { return new XYZPayrollPermissionCollection(); } } The instance variables in this class are required to hold the information about the actions--even though our superclass makes references to actions, it doesn't provide a manner in which to store them or process them, so we have to provide that logic. That logic is provided in the parse() method; we've chosen the common convention of having the action string treated as a list of actions that are separated by commas and whitespace. Note also that we've stored the actual actions as bits in a single integer--this simplifies some of the later logic. As required, we've implemented the equals() and hashCode() methods--and we've done so rather simply. We consider objects equal if their names are equal and their masks (that is, their actions) are equal, and construct a hash code accordingly. Our implementation of the getActions() method is typical: we're required to return the same action string for a permission object that was constructed with an action list of "view, update" as for one that was constructed with an action list of "update,view". This requirement is one of the prime reasons why the actions are stored as a mask--because it allows us to construct this action string in the proper format. Finally, the implies() method is responsible for determining how wildcard and other implied permissions are handled. If the name passed to construct our object is an asterisk, then we match any other name; hence, an object to represent the permissions of the HR department might be constructed as: Class Definitionnew XYZPayrollPermission("*", "view, update") When the implies() method is called on this wildcard object, the name will always match, and because the action mask has the complete list of actions, the mask comparison will always yield the mask that we're testing against. If the implies() method is called with a different object, however, it will only return true if the names are equal and the object's mask is a subset of the target mask. Note that we also might have implemented the logic in such a way that permission to perform an update implies permission to perform a view simply by changing the logic of testing the mask--you're not limited only to wildcard matching in the implies() method. 5.2.4. The BasicPermission ClassIf you need to implement your own permission class, the BasicPermission class (java.security.BasicPermission) provides some useful semantics. This class implements a basic permission--that is, a permission that doesn't have actions. Basic permissions can be thought of as binary permission--you either have them, or you don't. However, this restriction does not prevent you from implementing actions in your subclasses of the BasicPermission class (as the PropertyPermission class does). The prime benefit of this class is the manner in which it implements wildcards. Names in basic permissions are considered to be hierarchical, following a dot-separated convention. For example, if the XYZ corporation wanted to create a set of basic permissions, they might use the convention that the first word of the permission always be xyz: xyz.readDatabase, xyz.writeDatabase, xyz.runPayrollProgram, xyz.HRDepartment.accessCheck, and so on. These permissions can then be specified by their full name, or they can be specified with an asterisk wildcard: xyz.* would match each of these (no matter what depth), and * would match every possible basic permission. The wildcard matching of this class does not match partial names: xyz.read* would not match any of the permissions we just listed. Further, the wildcard must be in the rightmost position: *.readDatabase would not match any basic permission. The BasicPermission class is abstract, although it does not contain any abstract methods, and it completely implements all the abstract methods of the Permission class. Hence, a concrete implementation of the BasicPermission need only contain a constructor to call the correct constructor of the superclass (since there is no default constructor in the BasicPermission class). Subclasses must call one of these constructors:
5.2.5. Permission CollectionsThe access controller depends upon the ability to aggregate permissions so that it can easily call the implies() method on all of them. For example, a particular user might be given permission to read several directories: perhaps the user's home directory (/home/sdo/-) and the system's temporary directory (/tmp/-). When the access controller needs to see if the user can access a particular file, it must test both of these permissions to see if either one matches. This can be done easily by aggregating all the file permissions into a single permission collection. Every permission class is required to implement a permission collection, then, which is a mechanism where objects of the same permission class may be grouped together and operated upon as a single unit. This requirement is enforced by the newPermissionCollection() method of the Permission class. The PermissionCollection class (java.security.PermissionCollection) is defined as follows:
There are three basic operations that you can perform on a permission collection:
The javadoc documentation of this class claims that a permission collection is a collection of heterogeneous permission objects. Forget that idea; introducing that notion into permission collections vastly complicates matters, and the issue of a heterogeneous collection of permission objects is better handled elsewhere (we'll see how a little bit later). As far as we're concerned, the purpose of a permission collection is to aggregate only permission objects of a particular type. Permission collections are typically implemented as inner classes, or at least as classes that are private to the package in which they are defined. There is, for example, a corresponding permission collection class for the FilePermission class, one for the SocketPermission class, and so on. None of these collections is available as a public class that we can use in our own program. Hence, in order to support the newPermissionCollection() method in our XYZPayrollPermission class, we'd need to do something like this: Class Definitionpublic class XYZPayrollPermissionCollection extends PermissionCollection { private Hashtable permissions; private boolean addedAdmin; private int adminMask; XYZPayrollPermissionCollection() { permissions = new Hashtable(); addedAdmin = false; } public void add(Permission p) { if (!(p instanceof XYZPayrollPermission)) throw new IllegalArgumentException( "Wrong permission type"); XYZPayrollPermission xyz = (XYZPayrollPermission) p; String name = xyz.getName(); XYZPayrollPermission other = (XYZPayrollPermission) permissions.get(name); if (other != null) xyz = merge(xyz, other); if (name.equals("*")) { addedAdmin = true; adminMask = xyz.mask; } permissions.put(name, xyz); } public Enumeration elements() { return permissions.elements(); } public boolean implies(Permission p) { if (!(p instanceof XYZPayrollPermission)) return false; XYZPayrollPermission xyz = (XYZPayrollPermission) p; if (addedAdmin && (adminMask & xyz.mask) == xyz.mask) return true; Permission inTable = (Permission) permissions.get(xyz.getName()); if (inTable == null) return false; return inTable.implies(xyz); } private XYZPayrollPermission merge(XYZPayrollPermission a, XYZPayrollPermission b) { String aAction = a.getActions(); if (aAction.equals("")) return b; String bAction = b.getActions(); if (bAction.equals("")) return a; return new XYZPayrollPermission(a.getName(), aAction + "," + bAction); } } Note the logic within the implies() method--it's the important part of this example. The implies() method must test each permission in the hashtable (or whatever other container you've used to store the added permissions), but it should do so efficiently. We could always call the implies() method of each entry in the hashtable, but that would clearly not be efficient--it's better to call only the implies() method on a permission in the table that has a matching name. The only trick is that we won't find a matching name if we're doing wildcard pattern matching--if we've added the name "*" to the table, we'll always want to return true, even though looking up the name "John Smith" in the table will not return the administrative entry. Implementing this wildcard pattern matching efficiently is the key to writing a good permission collection. When you use (or subclass) one of the concrete permission classes that we listed earlier, there is no need to provide a permission collection class--all concrete implementations provide their own collection. In addition, there are two other cases when you do not need to implement a permission collection:
If you implement your own PermissionCollection class, you must keep track of whether it has been marked as read-only. There are two methods invlolved in this:
A permission collection is expected to throw a security exception from its add() method if it has been marked as read-only. Note that the read-only instance variable is private to the PermissionCollection class, so subclasses will have to rely on the isReadOnly() method to test its value. 5.2.6. The Permissions ClassSo far, we've spoken about permission collections as homogeneous collections: all permissions in the XYZPayrollPermissionCollection class are instances of the XYZPayrollPermission class; a similar property holds for other permission collections. This idea simplifies the implies() method that we showed above. But to be truly useful, a permission collection needs to be heterogeneous, so it can represent all the permissions a program should have. A permission collection really needs to be able to contain file permissions, socket permissions, and other types of permissions. This idea is present within the PermissionCollection class; conceptually, however, it is best to think of heterogeneous collections of permissions as encapsulated by the Permissions class (java.security.Permissions):
This class contains a concrete implementation of a permission collection that organizes the aggregated permissions in terms of their individual, homogenous permission collections. You can think of a permissions object as containing an aggregation of permission collections, each of which contains an aggregation of individual permissions. For example, let's consider an empty permissions object. When a file permission is added to this object, the permissions object will call the newPermissionCollection() method on the file permission to get a homogeneous file permission collection object. The file permission is then stored within this file permission collection. When another file permission is added to the permissions object, the permissions object will place that file permission into the already existing file permission collection object. When a payroll permission object is added to the permissions object, a new payroll permission collection will be obtained, the payroll permission added to it, and the collection added to the permissions object. This process will continue, and the permissions object will build up a set of permission collections. When the implies() method of the permissions object is called, it will search its set of permission collections for a collection that can hold the given permission. It can then call the implies() method on that (homogenous) collection to obtain the correct answer. The Permissions class thus supports any arbitrary grouping of permissions. There is no need to develop your own permission collection to handle heterogeneous groups. Copyright © 2001 O'Reilly & Associates. All rights reserved. |
|