The cornerstone of COM+ access control is role-based security. A role is a symbolic category of users who share the same security privileges. When you assign a role to an application resource, you grant access to that resource to whoever is a member of that role.
The best way to explain role-based security is by demonstration. Suppose you have a COM+ banking application. The application contains one component, the bank component. The bank component supports two interfaces that allow users to manage bank accounts and loans, defined as:
interface IAccountsManager : IUnknown { HRESULT TransferMoney([in]int nSum,[in]DWORD dwAccountSrc, [in]DWORD dwAccountDest); HRESULT OpenAccount([out,retval]DWORD* pdwAccount); HRESULT CloseAccount([in]DWORD dwAccount); HRESULT GetBalance([in]DWORD dwAccount,[out,retval]int* pnBalance); }; interface ILoansManager : IUnknown { HRESULT Apply([in]DWORD dwAccount,[out,retval]BOOL* pbApproved); HRESULT CalcPayment([in]DWORD dwSum,[out,retval]DWORD* pdwPayment); HRESULT MakePayment([in]DWORD dwAccount,[in]DWORD dwSum); };
During the requirements-gathering phase of the product development, you discovered that not every user of the application should be able to access every method. In fact, there are four kinds of users:
The bank manager, the most powerful user, can access all methods on all interfaces of the component.
The bank teller can access all methods of the
IAccountsManager
interface, but is not authorized
to deal with loans. In fact, the application is required to prevent a
teller from accessing any ILoansManager
interface
method.
Similarly, the loan consultant can access any method of the
ILoansManager
interface, but a consultant is never
trained to be a teller and may not access any
IAccountsManager
interface method.
A bank customer can access some of the methods on both interfaces. A customer can transfer funds between accounts and find the balance on a specified account. However, a customer cannot open a new account or close an existing one. The customer can make a loan payment, but cannot apply for a loan or calculate the payments.
If you were to enforce this set of security requirements on your own, you would face an implementation nightmare. You would have to manage a list of who is allowed to access what and tightly couple the objects to the security policy. The objects would have to verify who the caller is and whether the caller has the right credentials to access them. The resulting solution would be fragile. Imagine the work you would have to do if these requirements were to change.
Fortunately, COM+ makes managing such a security access policy easy.
After importing the bank component into a COM+ application (be it a
server or a library application), you need to define the appropriate
roles for this application. Every COM+
application has a folder called Roles. Expand
the Roles folder, right click on it, and select
New from the context menu. Type Bank Manager
into the dialog box that comes up and click OK. In the
Roles folder, you should see a new item called
Bank Manager. Add the rest of the roles:
Customer
, Teller
, and
Loans Consultant
. The application should look
like Figure 7-1.
You can now add users to each role. You can add any user with an account on the machine or the domain. Every role has a Users folder under which you add registered users from your domain or the machine local users. For example, navigate to the Users folder of the Customer role, right-click the Users folder, and select New from the Context menu. In the dialog box, select the users who are part of the Customer role, such as Joe Customer (see Figure 7-2). You can populate this role and the remaining roles in the bank application with their users.
The next step is to grant access to components, interfaces, and methods for the various roles in the application, according to the bank application requirements. Display the bank component properties page and select the Security tab. The tab contains the list of all roles defined for this application. Check the Manager role to allow a manager access to all interfaces and methods on this component (see Figure 7-3). When you select a role at the component level, that role can access all interfaces and methods of that component. Make sure that the “Enforce component level access check” checkbox under Authorization is selected. This checkbox, your component access security switch, instructs COM+ to verify participation in roles before accessing this component.
Next, configure
security at the interface level. Display
the IAccountsManager
interface properties page,
and select the Security tab. Select the Teller role to grant access
to all methods in this interface to any member of the Teller role
(see Figure 7-4). The upper portion of the
interface security tab contains inherited
roles
—roles
that were granted access at the component level, and thus access to
this interface as well. Even if the Bank Manager role is not checked
at the IAccountsManager
interface level, that role
can still access the interface.
Similarly, configure the ILoansManager
interface
to grant access to the Loans Consultant role. The Bank Manager should
also be inherited in that interface. Note that the Loans Consultant
cannot access any method on the IAccountsManager
interface, just as the requirements stipulate.
Finally, you can configure access rights at the
method level. A customer should be able to invoke the
GetBalance( )
and TransferMoney( )
methods on the IAccountsManager
interface, and the MakePayment( )
method on the
ILoansManager
interface, but no other methods.
Granting access at the method level is similar to granting it at the
interface or component level. For example, to configure the
GetBalance( )
method, display that method’s
Properties page, select its Security tab and check the Customer role
(see Figure 7-5). The method’s Security tab
shows inherited roles from the interface and component levels. COM+
displays roles inherited from the component level with a component
icon; it shows roles inherited from the interface level with an
interface icon.
Because of the inherited nature of roles, you can deduce a simple guideline for configuring roles: put the more powerful roles upstream and the more restricted roles downstream.
For all practical purposes, COM+ role-based access control gives you ultimate flexibility with zero coding. It gives you this flexibility because access control at the method level is usually granular enough. Role-based security offers a scalable solution that does not depend on the number of system users. Without it, you would have to assign access rights for all objects and resources manually, and in some cases you would have to impersonate users to find out whether they have the right credentials. (In Section 7.8, you will see how an object can impersonate a caller.) Configurable role-based security is an extensible solution that makes it easy to modify a security policy. Like any other requirement, your application’s security requirements are likely to change and evolve over time, but now you have the right tool to handle it productively.
Role-based access control is not limited to configurations made with the Component Services Explorer. You can build more granular security policies programmatically if you need to, using role-based security as a supporting platform.
Roles map nicely to terminology from your application’s domain. During the requirements analysis phase, you should aspire to discern user roles and privileges, in addition to discovering interfaces and classes. Focus your efforts on discovering differences in the roles users play that distinguish them from one another, rather than placing explicit permissions on each object in the system. As you saw in the bank example, roles work very well when you need to characterize groups of users based on what actions those users can perform. However, roles don’t work well in a couple of cases. First, they don’t work well when access decisions rest on the identity of a particular user: for example, if only the bank teller Mary Smiling is allowed to open an account. Second, they don’t work well when access decisions rest on special information regarding the nature of a particular piece of data: for example, when bank customers cannot access accounts outside the country. Role-based security is a service that protects access to middle-tier objects. Middle-tier objects should be written to handle any client and access any data. Basing your object behavior on particular user identities does not scale. Forcing your objects to know intimate details about the data does not scale well either. Each security mechanism has its limitations—if your application requires you to implement this sort of behavior, you may want to look at other options, such performing the security access checks at the database itself.
When designing effective roles, try to avoid a very intricate role-based policy. A policy with many roles that allocates users to multiple roles may be too complicated. Role-based security should be a straightforward solution with crisp distinctions between roles. Avoid defining roles with ambiguous membership criteria. The simpler the solution, the more robust and maintainable it will be. Your application administrator should be able to map users to roles instantly. Use meaningful, self-describing names for roles, borrowing as much as possible from the application domain’s terms and vocabulary. For example, Super User is a bad role name, whereas Bank Manager is a good name (even though your application would function just fine with the former).
Occasionally, you will be tempted to model a real-life situation and define numerous roles. Maybe different branches of the bank have different policies describing what a teller can do. Try to collapse roles as much as possible. You can do this either by refactoring your interfaces (deciding what methods will be on what interface and which component supports which interface) or by defining new interfaces and components. Breaking the system into more granular COM+ applications, each with its own small set of roles, is another design solution used to cope with numerous roles. This solution would probably be a better modeling of the system in other respects as well.
Avoiding numerous roles also improves performance. On each call, COM+ must scan the list of roles to find out whether the caller is a member of a role that is granted access.
Roles are defined at the application level, but they are actually part of every component’s design. If you write a standalone COM+ component that will be placed in COM+ application managed by someone else, you need to have in your documentation explicit instructions describing how to configure security for the hosting application. You need to document that your component needs its access control turned on for this application, the required authentication level, the roles that should be defined for this application, and the criteria that should be used to allocate users for your roles. You need to stipulate which methods and interfaces each role should be granted access to and which roles are granted access to the entire component.
Roles are an integral part of your design, but allocation of users to roles is part of your application deployment. The application administrator should make the final allocation of users to roles at the customer site. Because you need to make the administrator’s job as easy as possible, your application should already have predefined roles, and the administrator should only need to allocate users to roles. When adding users to roles, populating the roles with Windows 2000 user groups instead of individual users is wise. Groups also appear on the same list as users, such as in Figure 7-2, in the Bank Tellers group. By assigning groups to roles, the application is automatically configured to handle the new user correctly when a new user is added to a domain user group. The same is true when a user is removed from a Windows user group or removed from one group and added to another (for example, when Mary Smiling is promoted to a bank manager position). When you assign groups to roles, your application reacts transparently to normal events in the application domain.
If you target international markets, you should localize your roles and have them translated into the local language. In many cases, application administrators will be local hires on the foreign market, and properly translated roles can make a world of difference.
When providing the best support for your application administrator, you should clearly document the role-based policy you design, whether or not role membership is obvious to you. In particular, use the description field available for each role, as shown in Figure 7-6. The description should be concise. If you cannot describe who should belong to the role in three lines, the role is probably too complex.
Building a helper administrative utility to add users to roles programmatically, using the COM+ Catalog’s interfaces and components, may also be worthwhile; it saves the application administrator the trouble of learning how to use the Component Services Explorer. The utility should present to the administrator a familiar user interface, preferably the same user interface standard as the application itself. The utility should display the users selection dialog box to the administrator and add the selected users to the appropriate roles. When you export a COM+ application, the Application Export Wizard gives you the option of exporting the user identities with the roles (see Figure 7-7)
This option should only be used by the application administrator when making cloned installations at a particular site, from one machine to another. Remember that roles are part of the design, while allocation of users to roles is part of deployment. In fact, exporting user information from one deployment site to another may constitute a security breach. Most customers would not like a list of their employees, their usernames, and the roles they play in the organization available at large, let alone at some other company’s site. As a developer, “export user identities with roles” is of little use to you.