As well as using declarative security in MTS by adding roles to packages, we can add code to a component that exerts finer control over the way it behaves. An excellent example of this in our Wrox Car Co application would be where we wanted to control access in different ways for different 'ranks' of people in the showroom. For example we might want to limit the total size of an order if it was being placed by a salesperson, and permit only managers to place fleet orders (say more than one car to the same customer).
Why Use Programmatic Security?
To do this with only declarative security would mean creating a separate component, placing it in a separate package, and having salespersons and managers log on and use the appropriate component. Instead we can add both user account groups (Sales staff and Showroom managers) to the roles of the component and then differentiate between them inside the component using programmatic security.
We can even use the MTS objects to get the username of the caller, although this technique is generally frowned upon. If we hard-code account names into the component we have to rebuild it when the account details change—when a new manager joins the company for example. By using the MTS roles, we just add users to, and remove them from, the NT groups assigned to the roles for this component's package.
How Programmatic Security Works
To use programmatic security we take advantage of methods and other objects that MTS makes available within our components, via the
object. The two methods are:
ObjectContext
IsSecurityEnabled
- returns True
if security is enabled for the component, or False
if not. This method always returns False
for components installed in Library packages—i.e. those that are running in the client's address space.IsCallerInRole
- accepts a string that is the name of a role and returns True
if the current user account is a member of that role. Note that this is the account used to activate the component, and so may not be the original user's account. It could be the identity of another component.
Using these methods is easy enough. However, bear in mind that an error should prevent access, and not allow it by default. In other words, your code needs to be fail-safe. The sequence of events is to check first that the component is running under MTS, then that security is enabled, and finally see if the caller is listed in the specified role of the package. The following example returns
if the user is part of the role specified in the True
argument, and strRoleName
if not:False
Function ValidateCaller(strRoleName As String) As Boolean
On Error GoTo Validate_Error
Dim objContext As ObjectContext
Set objContext = GetObjectContext()
'if objContext is Nothing we're not running in MTS
If objContext Is Nothing Then
ValidateCaller = False
Exit Function
End If
'see if security is enabled
If objContext.IsSecurityEnabled() = False Then
IsInRole = False
Exit Function
End If
'OK, so call the IsCallerInRole method
ValidateCaller = objContext.IsCallerInRole(strRoleName)
Exit Function
Validate_Error:
ValidateCaller = False
Exit Function
End Function
We could then use this function to limit fleet sales in our Wrox Car Co application to members of the Showroom managers NT account group with:
...
If intNumberCars > 1 Then
If ValidateCaller("Showroom managers") Then
'OK to process order
Else
'Error, user is not a manager
End If
End If
...
Finding The Original and Direct Users
The
property of the Security
object returns a reference to the component's ObjectContext
object. This provides four methods, all of which we can use to get details of the original user account that started off the process within which our component is executing.SecurityProperty
There are two situations to consider. The original process is the process that started off the whole chain of events. This will be the user who accessed the application via IIS in our Web based examples. In effect, the name of the original process is that of the 'real' user's account.
The base process is that which initiated the current process. If we are in a component inside a transaction, for example, the base process will be the component that initiated the transaction. It will always be a component or user outside the current package, but may not be the same as the original process.
For each of these two processes, we can retrieve the account username for either the caller or the creator. In general these will be the same. They will differ when one process creates an instance of a component and then passes a reference to it on to another component, which then calls a method in the new component. In this case the creator and the caller are different processes, and so could have different usernames.
|
The username of the original process that called the currently executing method. |
|
The username of the original process that directly created the current object. |
|
The username of the base process that called the currently executing method. |
|
The username of the base process that directly created the current object. |
As an example, look at the following (somewhat contrived) example. The diagram shows a process A, which creates an instance of an object C. It then passes a reference to this object on to process B, which calls a method in that object. Object C then calls a method in object D, creating an instance of it in the process:
In this case, within the security context of object D, the original caller is process B while the direct caller is object C. The original creator, however, is process A; and the direct creator is object C.
To get the username of the original caller, we could use the code:
Dim strUserName As String
Dim objContext As ObjectContext
objContext = GetObjectContext()
strUserName = objContext.Security.GetOriginalCallerName()
As we noted earlier, using these methods to retrieve details of user accounts for 'real' users (i.e. people, rather than package identities) is generally not a good idea. It means that you will have to change the code if you need to add or remove users in the future. It's far better to design the security structure of the application around declarative security and the two
methods ObjectContext
and IsSecurityEnabled
. Then you can use the Windows NT User Manager to add and remove users, and the changes will automatically be picked up by MTS.IsCallerInRole
As you've seen in this section of the chapter, MTS security techniques are at the heart of any DNA application. Using declarative security, combined where appropriate with programmatic security, means that we can exert fine control over which users can access our application, and which parts of the application they can use. The next step is to consider how we configure other parts of our application to work securely with MTS.