Role-Based Security

In Chapter 9, we looked at the role-based security mechanism offered by .NET. A similar role-based security service is offered by COM+. There are a few noteworthy differences:

  1. The role-based security mechanism under COM+ works only for Windows principals. .NET offers a more generalized role-based security mechanism; the roles can be based either on Windows principals (WindowsPrincipal) or on generic principals (GenericPrincipal).

  2. Under .NET, the roles defined by WindowsPrincipal are tied to the local Windows user groups. Under COM+, however, the roles can be arbitrarily defined. Each role can be assigned zero or more individual users or user groups.

Let's extend our employee salary program to deal with role-based security. We define an interface ISalary to obtain or to update an employee's salary. The interface is shown here:

// Project RoleBasedSecurity/Employee

public interface ISalary {
     long GetSalary();
     void SetSalary(long newSalary);
}

The roles that the serviced component will use can be defined declaratively at the assembly level using the attribute SecurityRoleAttribute. For our example, we define two roles, Manager and Employee, as illustrated in the following code excerpt:

[assembly: SecurityRole("Manager", Description="The big wigs")]
[assembly: SecurityRole("Employee")]

When the assembly is installed in the COM+ catalog, the specified roles are created at the COM+ application level. Optionally, the roles can be provided a descriptive text.

By default, the roles that are created at installation time do not have any users associated with them. However, the system administrator (or any privileged user) can then add or remove Windows users (or user groups) for each role using the Component Services snap-in.

It is possible to declaratively add the local user group Everyone to a role. This is done by setting a property, SetEveryoneAccess, on the class SecurityRoleAttribute to true, as illustrated in the following revised definition of the Employee role:

[assembly: SecurityRole("Employee", SetEveryoneAccess=true)]

This statement results in the Everyone local group being added to the Employee role when this assembly is installed in the COM+ catalog. However, opening an application to everyone may not always be a good idea (although it is convenient for debugging purposes). Evaluate your case to see if you need this feature for any of the roles your application defines.

Declarative Access Check

The first step in performing any access check is to configure your application to enforce access checks. This can be done using an assembly-level attribute, ApplicationAccessControlAttribute, in your code. The default constructor for this attribute causes the access checks to be enforced. However, you can also use an overloaded constructor to explicitly turn on the access check. Consequently, the following two statements behave identically:

[assembly: ApplicationAccessControl]
[assembly: ApplicationAccessControl(true)]

Access checks can be performed either at the process level or a finer level that includes components, interfaces, and interface methods. This behavior is controlled by a property, AccessChecksLevel, that ApplicationAccessControlAttribute defines. The options that can be set on this property are defined as an enumeration of type AccessCheckLevelOption. The possible options are Application (for a process-level check) and ApplicationComponent (for a finer level check).

The process-level access check makes sense only if the COM+ application is configured to run as a server (in a separate process dllhost.exe). The following code excerpt shows how to enforce the process-level access check:

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(AccessChecksLevel=
     AccessChecksLevelOption.Application)]

When an application is configured for a process-level access check, only those users present in the application-specific security roles can access the application. Recall that users can be added or removed from a role using the Component Services snap-in.

A process-level access check is a good first-level protection against unknown users. However, most applications desire a finer level of control on the component or interface method an individual user can access. This is where AccessChecksLevelOption.ApplicationComponent is useful:

[assembly: ApplicationActivation(ActivationOption.Server)]
[assembly: ApplicationAccessControl(AccessChecksLevel=
     AccessChecksLevelOption.ApplicationComponent)]

Applying the ApplicationComponent option for a server application enables the access check at the process level as well as the finer levels.

Note that the ApplicationComponent option can be applied to a library application as well. However, in this case there will not be any process-level access check; security checks occur only at the finer levels.

After applying the ApplicationComponent option, the next step is to identify each serviced component within the application on which the finer level access checks will be performed. This is done using an attribute ComponentAccessControlAttribute, as shown in the following code excerpt:

// Project RoleBasedSecurity/Employee

[ComponentAccessControl]
public class Employee : ServicedComponent, ISalary {
     ...
}

The default constructor for ComponentAccessControlAttribute turns on the access check for the serviced component. However, you can also turn the access on explicitly by using an overloaded constructor that takes a Boolean parameter. Consequently, the following two statements behave identically:

[ComponentAccessControl]
[ComponentAccessControl(true)]

Note that applying ComponentAccessControlAttribute is a must for each serviced component on which you intend to perform the finer level access checks. Without this attribute turned on, you will run into unpredictable behavior.

Now we are ready to declaratively define the security roles that can access the serviced component, any of the interfaces it supports, or the methods on the interface. This is done using our familiar SecurityRoleAttribute class. The following code excerpt shows how the Employee-serviced component can be set to be accessed only by users belonging to either the Manager or the Employee roles:

[SecurityRole("Manager")]
[SecurityRole("Employee")]
[ComponentAccessControl]
public class Employee : ServicedComponent, ISalary {
     ...
}

The security roles can be associated further at the interface or the interface method level. For example, it makes sense that only managers be set to change employee salaries. The following code excerpt shows how this can be done declaratively:

[SecurityRole("Manager")]
public void SetSalary(long newSalary) {
     ...
     m_Salary = newSalary;
}

Note that if you assign the role at the component, interface, or method level but not at the assembly level, the role is automatically added to the application during the assembly registration. However, it is a good practice to define all the possible roles at the assembly level.

Programmatic Access Check

A declarative access check is a great way to provide role-based security. However, declarative programming has its own limitations. For example, it cannot prevent any employee from accessing any other employee's salary information.

To provide programmatic access checks, the .NET/COM+ plumbing provides a class, SecurityCallContext. This class encapsulates security information about all the callers in the call chain. Table 10.3 lists some important properties and methods available on this class.

Table 10.3. SecurityCallContext Members
MemberDescription
NumCallersNumber of callers in the call chain.
CallersCollection of callers. Each caller is represented by a SecurityIdentity object.
DirectCallerA SecurityIdentity object representing the direct caller of the method.
OriginalCallerA SecurityIdentity object representing the original caller of the method.
IsSecurityEnabledChecks if the security check is enabled in the current context.
IsCallerInRoleChecks if the direct caller is in the specified role.
IsUserInRoleChecks if the specified user is in the specified role.

The current security call context can be obtained by using a static property, SecurityCallContext.CurrentCall. Using this context, you can check, for example, whether or not the direct caller is in a specific role. You can also perform additional checks using the direct caller's account name. The following code excerpt checks the caller to ensure that only managers and the employee himself or herself can obtain the salary information:

// Project RoleBasedSecurity/Employee

public long GetSalary() {
     ...
     SecurityCallContext ctx = SecurityCallContext.CurrentCall;
     if (!ctx.IsSecurityEnabled) {
       throw new Exception(
          "Component not configured correctly");
     }

     if (ctx.IsCallerInRole("Manager")) {
          // managers always have access
          return m_Salary;
     }

     String caller = ctx.DirectCaller.AccountName;

     // Case-insensitive comparison
     if (0 == String.Compare(caller, m_employeeName)) {
       // only an employee can look up his/her salary
       throw new Exception("You are not authorized");
     }

     return m_Salary;
}

It is important to understand that for the programmatic access to work on a serviced component, you still need to set ApplicationAccessControl to ApplicationComponent as well as ComponentAccessControl to true for the component. Without these settings, the security call context may return incorrect results. To ensure that these settings are property enabled, you can check the property IsSecurityEnabled on the security call context, as shown in the preceding code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.129.100