Chapter 8 described the security models of Microsoft Windows NT and COM as they existed before the introduction of MTS. As you'll recall, each Windows NT user account and group account has an associated security ID (SID). When a user tries to access a distributed application from across the network, the system authenticates the user's credentials and checks whether the user has the proper authorization. Administrators authorize access to users in a distributed application by granting access permissions and optionally launch permissions to Windows NT user accounts and group accounts, using a tool such as DCOMCNFG.exe. This tool's editor lets you modify the discretionary access control list (DACL) associated with the application's AppID.
Identity is also an important concept in COM security. An administrator should configure the AppID for each distributed application using a RunAs setting so that the system knows which Windows NT user account to use as the application's security principal. (An application that hasn't been explicitly assigned a RunAs setting runs under the identity of the launching user.) When the Service Control Manager (SCM) launches an application, it uses the RunAs setting to assign an identity to the application's process. Objects created inside the application run with the same credentials as the application's security principal. Identity is also known as activation security because it's set up when the application is activated.
Chapter 8 also explained how COM offers both declarative security and programmatic security. Declarative security is beneficial because it transfers the responsibility for securing a distributed application from programmers to administrators. Programmatic security provides more control and flexibility than declarative security. However, there's little you can do on the programmatic side of COM security using Visual Basic. Unless you're prepared to write some supporting code in a C++ DLL, you must run your Visual Basic applications using declarative security settings stored in the local Registry.
As you know, COM security is built on top of Windows NT security and the Remote Procedure Call (RPC) layer. COM can leverage the accounts database and SID management as well as authentication and authorization checking from the underlying system. It's important to understand how all this works because MTS security is layered on top of COM security. MTS uses COM authentication to validate users. MTS also uses COM's activation security to control which Windows NT user account serves as the identity for a server package.
However, MTS doesn't use the same authorization scheme as COM. One shortcoming of the access control provided by COM is that it lacks sufficient granularity. You can configure only two types of permissions for an application: access permissions and launch permissions. This means that access control is an all-or-nothing proposition. You can either grant or deny access permissions on an application-wide basis for each Windows NT user or group. Once you let a user in the door, you can't control what the user can do. MTS replaces COM's access scheme with its own to provide a greater degree of granularity and control.
In Windows NT 4, every COM application should be configured with its own AppID. An MTS application is no exception. (An MTS server package is an MTS application.) MTS creates and manages a hidden AppID for every server package. Using the MTS Explorer, you can easily change the authentication level and identity settings for a server package. When you change these settings, MTS modifies the hidden AppID for you.
The designers of MTS decided to replace COM access control with their own version. The AppID behind an MTS server package is configured to use the default machinewide launch permissions; however, when the package is launched, MTS grants access permissions to all users. In effect, MTS turns off COM's access control. All users must be able to get past the security checkpoints of COM in order to reach those of MTS.
MTS extends COM security with its own security model using the concept of a role. A given user can be assigned to multiple roles. A role is a set of users within an MTS application that have the same security profile. When you design a secured MTS application, you should define an appropriately named role for each type of user. For example, you can design an application with three roles: Reader, Writer, and Manager. In MTS, both declarative and programmatic security checks are performed on the roles you define instead of on SIDs, as in COM. The MTS security model thus offers more control and flexibility than does COM.
Let's look at declarative security first. When you create a new server package, declarative authorization checking is turned off by default. You can turn it on by selecting the Enable Authorization Checking option on the Security tab of the server package's Properties dialog box. You can then configure which roles (and therefore which users) have permission to invoke methods on objects running inside the application. There are four steps to configuring declarative security checks with roles in an MTS server package. Using the MTS Explorer, you do the following:
One of the biggest benefits of roles is that you can preconfigure the permissions for the entire application in the development environment. Permissions in MTS have no dependencies on the SIDs from the Windows NT accounts database. This makes the role-based model much more flexible than the COM security model, in which access permissions are assigned directly to SIDs. The concept of roles is more natural for developers, and the use of roles eases deployment of a single package in multiple production environments.
Another benefit of this model is that you can configure access permissions on a component-by-component as well as an interface-by-interface basis. This declarative security scheme provides much more control than COM security. You can also configure declarative security at the interface level, which provides yet another motivation to base the design of your Visual Basic applications on user-defined interfaces.
For example, you can create a CCustomers component that implements two user-defined interfaces, IQueryCustomer and IUpdateCustomer. When you set up the permissions for your application, you can configure every role in the package to have access to the IQueryCustomer interface but restrict access to the IUpdateCustomer interface for every role except Writer. Stop and think about the implications of this example. I simply can't overemphasize how powerful declarative security checks are at the interface level.
Yes, I know that user-defined interfaces bring along some additional complexity, which makes many Visual Basic programmers run and hide. It's easy to fall back on Visual Basic's ability to create and implicitly cast to the default interface behind the scenes. And certainly the Visual Basic team itself can be justly accused of de-emphasizing the importance of user-defined interfaces in COM programming, given the fact that their integrated development environment (IDE) has very little support for them. But here's my point. Even if you don't care to design applications that take advantage of polymorphism and run-time type information (covered in Chapter 2), you should consider implementing user-defined interfaces because of the advantages they bring to declarative security.
A role is defined within the scope of an MTS server package. That is, each role exists within a single MTS application (running process). When a server package is launched, the MTS container application (Mtx.exe) and the MTS executive (Mtxex.dll) work together to set up the role-based authorization checking scheme.
Package security isn't available with library packages. Library packages aren't secured because they are loaded into the address space of an unknown client application. There's no guarantee that the client application will be a secured MTS application. Furthermore, you must set up role-based authorization checking when the hosting process is launched. You can't do this with library packages because of their passive nature.
What this boils down to is that roles and role membership have no meaning within an MTS library package. When you attempt to create a role or assign role membership in a library package, the MTS Explorer displays a dialog box indicating that package security isn't available. You must be careful because a client application can defeat the MTS security model by creating objects directly from a library package. When a non-MTS client creates objects from a library package, the application is unsecured and the role-based security features of MTS won't function properly.
As you've seen, each class and interface can have one or more associated roles in its membership. If authorization checking is enabled, the caller must be in at least one role in order to successfully invoke a method, as shown in Figure 12-1. If MTS discovers that the caller doesn't belong to any role associated with the component or the interface in use, it fails the call and returns the well-known HRESULT E_ACCESSDENIED. If the caller is a Visual Basic application, the Visual Basic run time catches the HRESULT E_ACCESSDENIED and raises a trappable "Permission denied" error with an error number of 70.
Figure 12-1. MTS performs security checks for access control when a call arrives at a secured MTS application.
MTS performs authorization checks only on calls from outside the package, not on intrapackage (intraprocess) calls. Intraprocess calls are therefore much faster. However, because intraprocess calls are assumed to be secure, you must protect your applications from unintended security holes.
Let's say that you have component A and component B inside a server package in which you have enabled authorization checking. You've placed Role1 in the membership of component A and Role2 in the membership of component B, as shown in Figure 12-1. Any user who is a member of Role1 can directly invoke a method on objects of type A but not on objects of type B. But what happens if a Role1 user invokes a method on an object of type A, which in turn invokes a method on an object of type B? The call will succeed because there are no intraprocess security checks. You should understand the implications of this example when you design components that make intraprocess calls.
Calls between server packages can also be tricky. For example, what happens if client A makes a call on server package B, which then makes a call on server package C? You might expect server package C to perform its security checks using client A's security credentials, but this isn't the case. MTS performs its security checks from hop to hop as opposed to performing them from end to end. This means you must understand the difference between the direct caller and the original caller. The direct caller is the party one hop up the call chain, while the original caller is the party at the top of the call chain. MTS always performs its security checks using the credentials of the direct caller.
In this example, client A is the original caller and server package B is the direct caller. Server package C conducts its security checks using the Windows NT user account that serves as the identity for server package B. As you can see, you must be careful. When you grant a role access permissions to a component, you must be certain that the component itself doesn't make any intraprocess calls or interprocess calls that will permit access to other components that should be off-limits to the role in question.
As you've seen, the declarative security available in MTS (in its current release) offers access control at both the component and the interface level. COM+ will likely extend this control down to the method level. However, if you need method-level granularity now, you must resort to programmatic security. Fortunately, programmatic security is much easier in MTS than it is in COM. MTS exposes a handful of properties and methods through the ObjectContext interface, making programmatic security easily accessible to Visual Basic programmers.
MTS offers programmatic security to complement and extend what is possible through declarative security. You can write your own security checks using the ObjectContext interface. For example, you can test a call to see whether it's running inside a secured MTS application before you attempt a secured operation:
Const E_ACCESSDENIED = &H80070005 Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() If ObjCtx.IsSecurityEnabled() Then ' Conduct secured operation. Else ' Raise an access-denied error to caller. Err.Raise E_ACCESSDENIED, _ "MyDll.MyClass.MyMethod", _ "Permission denied" End If
IsSecurityEnabled tells you whether the current call is running inside a secured MTS application. This method always returns True when it's called from a component in an MTS server package. It also returns True when it's called from a library package that has been loaded by a server package. However, if the library package has been loaded into an unsecured client application, it will return False. If you want your code to run only within a secured MTS application, you can use the code in the previous example to raise a "Permission denied" error back to the caller if the library package has been loaded into an unsecured application.
You can also write code to test whether the caller belongs to a particular role. The IsCallerInRole method accepts a single string parameter of the role you want to test. This method returns True if either the Windows NT user account or one of the Windows NT group accounts of the caller maps to this role. IsCallerInRole always returns True when it's called inside an unsecured application. You should therefore call IsCallerInRole together with IsSecurityEnabled, like this:
Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() If ObjCtx.IsSecurityEnabled() And _ ObjCtx.IsCallerInRole("Manager") Then ' Conduct a secured, manager-only operation. Else Err.Raise E_ACCESSDENIED, _ "MyDll.MyClass.MyMethod", _ "Permission denied" End If
As you can see, IsCallerInRole lets you perform access checking at the method level. It also lets you perform conditional access checks. For example, you can write the SubmitOrder method so that only users in the role of Manager can submit a purchase order if the request exceeds $5,000:
Const E_ACCESSDENIED = &H80070005 Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() Dim Amount As Currency ' Determine the purchase amount. Amount = GetAmount() ' Check for manager role on any amount that exceeds $5,000. If Amount > 5000 Then If Not ObjCtx.IsSecurityEnabled() Or _ Not ObjCtx.IsCallerInRole("Manager") Then ' The caller isn't a manager, and the amount is more than $5,000. ObjCtx.SetAbort Dim ErrDesc As String ErrDesc = "Manager required when amount exceeds $5,000" Err.Raise E_ACCESSDENIED, , ErrDesc End If End If ' Now conduct secured operation.
You can call IsCallerInRole from a component inside a library package, but such a call can check only against the roles defined inside the server package in which the library package has been loaded. MTS also generates an mtsErrCtxRoleNotFound error if you call IsCallerInRole inside a secured MTS application and pass a role name that doesn't exist.
In addition to the role-based programmatic security you've just seen, MTS can provide the names of the Windows NT user accounts for both the object's creator and the object's caller. This is the code required to retrieve the original caller's Windows NT user account name:
Dim ObjCtx As ObjectContext Set ObjCtx = GetObjectContext() Dim Caller As String Caller = ObjCtx.Security.GetOriginalCaller()
This example shows how to use the Security property of the ObjectContext interface. The Security property lets you call the GetOriginalCallerName method. This method returns the caller's account name in an Authority\User format, as in MyDomain\TedP. In addition to GetOriginalCallerName, the Security object also exposes GetDirectCallerName, GetDirectCreatorName, and GetOriginalCreatorName. The concepts of original creator and direct creator work in exactly the same way as those of original caller and direct caller explained earlier in this chapter.