Designing a Permission
A permission represents the ability to access a protected resource or perform a protected operation. When you are implementing your own permission class, you must make several high-level design decisions. One of the first steps is to determine exactly what resource your custom permission is designed to protect.
You next want to decide whether there are any concerns about overlapping permissions. Although you want to avoid having two permissions that protect the same resource, in some situations you cannot reasonably avoid it. For example, the permission to access unmanaged code can also encompass other permissions, because code that is granted permission to access unmanaged code can do almost anything through an unmanaged API. However, when permission to access unmanaged code is not granted, you still need to grant permissions to access other specific resources. Therefore, it makes sense for permission to access unmanaged code to be separate from other permissions.
How do you know whether an overlap in permission coverage is manageable? There is no absolute answer, but one thing to think about is whether one of the permissions represents more fine-grained access than the other permission so that it will typically be more readily granted than the other permission. When this is the case, granting of access rights is easy to do in many cases, which makes the administrator's task easier.
After you have decided what resource your permission will protect and have resolved any issues about overlap of permissions, you must decide how finely grained access control should be. The answer to this question affects the way you design the variables that represent the state of the permission and determines whether administrators can configure access to the protected resource. It might also affect performance, ease of use, and other factors.
To illustrate some of these design issues, consider a few of the designs that could have been chosen for the FileIOPermission Class that the .NET Framework provides. Each design choice affects the variables that represent the permission's state.
- A single bit that means "use all files" or "use no files", depending on its value.
- Two bits meaning "read all files" and "write all files", or not, depending on their values.
- 26 bits meaning "use all files on the specified drive".
- An array of strings listing all files to which access is given.
Clearly, there are various tradeoffs to consider. For example, the single-bit permission is very simple, fast, and easy to understand, but it presents an all-or-nothing choice for administrators, which might not be desirable. Other choices that specify a more complex representation of permission state could slow performance to some degree. You must weigh these tradeoffs, and consider that you should not create more than one permission to protect the same resource. In general, you should design your permission class so that the state of the permission is as complex as necessary, without greatly impacting performance.
Although other designs are possible, most permissions follow one of the following standard design patterns or a combination thereof:
- Boolean permissions. This simplest kind of permission object holds one or more bits, each of which corresponds to "permission to do X". Either you have the permission or you do not. An example of this type of permission is the SecurityPermission class, whose state contains Boolean variables representing the right to do different things, such as permission to call into unmanaged code, each of which is either allowed or not.
- Levels of permissions. This more detailed form of permission has variables that represent each kind of access as a number from zero (meaning no access at all) to some higher number (meaning totally unrestricted access), with a few levels between the two. For example, you can use the UIPermission class to express varying levels of permission for using windows, from no UI permission to unrestricted UI permission, with a few gradations between the two.
- Object list permissions. This kind of permission provides a very detailed specification for what is and is not allowed. The FileIOPermission class is a good example of this type of permission because its state is represented by lists of files on which certain kinds of access are allowed. Permissions with lists are most useful for protecting resources that contain a large number of named objects.
In general, it is a good idea to minimize external dependencies in your custom permission class because every assembly your permission depends on will have to be loaded when the security system needs your permission class. Another reason to minimize dependencies is that every assembly used by your custom permission class (except Mscorlib) must be added to the full-trust list. (For more information, see Updating Security Policy.) If possible, you should place your custom permission and any attribute classes associated with it in a separate assembly, to reduce the likelihood that other assemblies will be loaded unnecessarily.
Note Custom permissions should either be marked as sealed (NotInheritable in Visual Basic) or have an inheritance demand placed on them. Otherwise, malicious callers are able to derive from your permission, potentially causing security vulnerabilities.