.NET has an elaborate component-oriented security model. .NET security model manages what the component is allowed to do and what permissions are given to the component and all its clients up the call chain. You can (and should) still manage the security attributes of your hosting COM+ application to authenticate incoming calls, authorize callers, and control impersonation level.
.NET also has what .NET calls role-based security, but that service is limited compared with COM+ role-based security. A role in .NET is actually a Windows NT user group. As a result, .NET role-based security is only as granular as the user groups in the hosting domain. Usually, you do not have control over your end customer’s IT department. If you deploy your application in an environment where the user groups are coarse, or where they do not map well to actual roles users play in your application, then .NET role-based security is of little use to you. COM+ roles are unrelated to the user groups, allowing you to assign roles directly from the application business domain.
The assembly
attribute
ApplicationAccessControl
is used to configure all
the settings on the hosting COM+ application’s Security tab.
You can use ApplicationAccessControl
to turn
application-level authentication on or off:
[assembly: ApplicationAccessControl(true)]
The ApplicationAccessControl
attribute has a
default constructor, which sets authorization to
true
if you do not provide a construction value.
Consequently, the following two statements are equivalent:
[assembly: ApplicationAccessControl] [assembly: ApplicationAccessControl(true)]
If you do not use the ApplicationAccessControl
attribute at all, then when you register your assembly, the COM+
default takes effect and application-level authorization is turned
off.
The ApplicationAccessControl
attribute has three
public properties you can use to set the access checks,
authentication, and impersonation level. The
AccessChecksLevel
property accepts an enum parameter of
type
AccessChecksLevelOption
, defined as:
public enum AccessChecksLevelOption { Application, ApplicationComponent }
AccessChecksLevel
is used to set the
application-level access checks to the process only
(AccessChecksLevelOption.Application
) or process
and component level
(AccessChecksLevelOption.ApplicationComponent
). If
you do not specify an access level, then the
ApplicationAccessControl
attribute’s constructors set the access level to
AccessChecksLevelOption.ApplicationComponent
,
the same as the COM+ default.
The
Authentication
property
accepts
an enum parameter of type
AuthenticationOption
, defined as:
public enum AuthenticationOption { None, Connect, Call, Packet, Integrity, Privacy, Default }
The values of AuthenticationOption
map to the six
authentication options discussed in Chapter 7. If
you do not specify an authentication level or if you use the
Default
value, the
ApplicationAccessControl
attribute’s
constructors set the authentication level to
AuthenticationOption.Packet
, the same as the COM+
default.
The Impersonation
property
accepts
an enum parameter of type
ImpersonationLevelOption
, defined as:
public enum ImpersonationLevelOption { Anonymous, Identify, Impersonate, Delegate, Default }
The values of ImpersonationLevelOption
map to the
four impersonation options discussed in Chapter 7.
If you do not specify an impersonation level or if you use the
Default
value, then the
ApplicationAccessControl
attribute’s
constructors set the impersonation level to
ImpersonationLevelOption.Impersonate
, the same as
the COM+ default.
Example 10-11
demonstrates using the
ApplicationAccessControl
attribute with a server
application. The example enables application-level authentication and
sets the security level to perform access checks at the process and
component level. It sets authentication to authenticate incoming
calls at the packet level and sets the impersonation level to
Identify
.
Example 10-11. Configuring a server application security
[assembly: ApplicationActivation(ActivationOption.Server)] [assembly: ApplicationAccessControl( true,//Authentication is on AccessChecksLevel=AccessChecksLevelOption.ApplicationComponent, Authentication=AuthenticationOption.Packet, ImpersonationLevel=ImpersonationLevelOption.Identify)]
A
library COM+ application has no
use for impersonation level, and it can only choose whether it wants
to take part in its hosting process authentication level (that is, it
cannot dictate the authentication level). To turn authentication off
for a library application, set the authentication property to
AuthenticationOption.None
. To turn it on, use any
other value, such as AuthenticationOption.Packet
.
Example 10-12 demonstrates how to use the
ApplicationAccessControl
to configure the security
setting of a
library
application.
Example 10-12. Configuring a library application security
[assembly: ApplicationActivation(ActivationOption.Library)] [assembly: ApplicationAccessControl( true,//Authentication AccessChecksLevel=AccessChecksLevelOption.ApplicationComponent, //use AuthenticationOption.None to turn off authentication, //and any other value to turn it on Authentication=AuthenticationOption.Packet)]
The
component attribute
ComponentAccessControl
is used to enable or disable access
checks at the component level. Recall from Chapter 7 that this is your component’s
role-based security master
switch. The ComponentAccessControl
attribute’s constructor accepts a Boolean parameter, used to
turn access control on or off. For example, you can configure your
serviced component to require component-level access checks:
[ComponentAccessControl(true)] public class MyComponent :ServicedComponent {...}
The ComponentAccessControl
attribute has an
overloaded default constructor that uses
true
for the attribute
construction. Consequently, the following two statements are
equivalent:
[ComponentAccessControl] [ComponentAccessControl(true)]
You
can use the Component Services Explorer to
add roles to the COM+ application hosting your serviced components.
You can also use the SecurityRole
attribute to add the roles at the
assembly level. When you
register the assembly with
COM+, the roles in the assembly are added to the roles defined for
the hosting COM+ application. For example, to add the Manager and
Teller roles to a bank application, simply add the two roles as
assembly attributes:
[assembly: SecurityRole("Manager")] [assembly: SecurityRole("Teller")]
The SecurityRole
attribute has two public
properties you can set. The first is
Description
.
Any text
assigned to the Description
property will show up
in the Component Services Explorer in the Description field on the
role’s General tab:
[assembly: SecurityRole("Manager",Description = "Can access all components")] [assembly: SecurityRole("Teller",Description = "Can access IAccountsManager only")]
The second property is the
SetEveryoneAccess
Boolean
property. If you set SetEveryoneAccess
to
true
,
then when the component
is registered, the registration process adds the user Everyone as a
user for that role, thus allowing everyone access to whatever the
role is assigned to. If you set it to
false
,
then no user is added
during registration and you have to explicitly add users during
deployment using the Component Services Explorer. The
SecurityRole
attribute sets the value of
SetEveryoneAccess
by default to
true
. As a result, the following statements are
equivalent:
[assembly: SecurityRole("Manager")] [assembly: SecurityRole("Manager",true)] [assembly: SecurityRole("Manager",SetEveryoneAccess = true)]
Automatically granting everyone access is a nice debugging feature;
it eliminates security problems, letting you focus on analyzing your
domain-related bug. However, you must suppress granting everyone
access in a release build, by setting the
SetEveryoneAccess
property to
false
:
#if DEBUG [assembly: SecurityRole("Manager")] #else [assembly: SecurityRole("Manager",SetEveryoneAccess = false)] #endif
The SecurityRole
attribute is also used to grant access for
a role to a component, interface, or method. Example 10-13 shows how to grant access to Role1 at the
component level, to Role2 at the interface level, and to Role3 at the
method level.
Example 10-13. Assigning roles at the component, interface, and method levels
[assembly: SecurityRole("Role1")] [assembly: SecurityRole("Role2")] [assembly: SecurityRole("Role3")] [SecurityRole("Role2")] public interface IMyInterface { [SecurityRole("Role3")] void MyMethod( ); } [SecurityRole("Role1")] public class MyComponent :ServicedComponent,IMyInterface {...}
Figure 10-2 shows the resulting role assignment in the Component Services Explorer at the method level. Note that Role1 and Role2 are inherited from the component and interface levels.
Figure 10-2. The resulting role assignment of Example 10-13 in the Component Services Explorer, as seen at the method level
If you only assign a role (at the component, interface, or method level) but do not define it at the assembly level, then that role is added to the application automatically during registration. However, you should define roles at the assembly level to provide one centralized place for roles description and configuration.
Sometimes it
is useful to verify
programmatically the caller’s role membership before granting
it access. Your serviced components can do that just as easily as
configured COM components. .NET provides you the helper class
SecurityCallContext
that gives you access to the security
parameters of the current call.
SecurityCallContext
encapsulates the COM+
call-object’s implementation of
ISecurityCallContext
, discussed in Chapter 7. The class
SecurityCallContext
has a public static property
called CurrentCall
.
CurrentCall
is a read-only property of type
SecurityCallContext
(it returns an instance of the
same type). You use the SecurityCallContext
object
returned from CurrentCall
to access the current
call. Example 10-14 demonstrates the use of the
security call context to verify a caller’s role membership,
using the same use-case as Example 7-1.
Example 10-14. Verifying the caller’s role membership using the SecurityCallContext class
public class Bank :ServicedComponent,IAccountsManager { void TransferMoney(int sum,ulong accountSrc,ulong accountDest) { bool callerInRole = false; callerInRole = SecurityCallContext.CurrentCall.IsCallerInRole("Customer"); if(callerInRole)//The caller is a customer { if(sum > 5000) throw(new UnauthorizedAccessException(@"Caller does not have sufficient credentials to transfer this sum")); } DoTransfer(sum,accountSrc,accountDest);//Helper method } //Other methods }
You should use the Boolean property
IsSecurityEnabled
of
SecurityCallContext
to verify that security is
enabled before accessing the IsCallerInRole( )
method:
bool securityEnabled = SecurityCallContext.CurrentCall.IsSecurityEnabled; if(securityEnabled) { //the rest of the verification process }