[ Team LiB ] Previous Section Next Section

10.2 Programming Role-Based Security

The .NET runtime enforces role-based security using techniques and syntax similar to those we described in Chapter 7 for code-access security. In your applications, you protect functionality by making role-based security demands that specify the identity or role that the thread's principal must contain. If the thread's principal does not contain the demanded identity and role, then the demand causes an exception.

10.2.1 Introducing the IIdentity and IPrincipal Interfaces

The System.Security.Principal namespace includes the IIdentity and IPrincipal interfaces to represent identities and principals. By using interfaces to represent identities and principals, .NET provides flexibility, which means that it is relatively easy to create concrete role-based security implementations to support many different authentication and authorization mechanisms. The .NET class library contains four concrete RBS implementations that use the IIdentity and IPrincipal interfaces:

Forms

Provides a role-based authentication mechanism for use in ASP.NET applications. Forms authentication only provides an implementation of IIdentity; we discuss Forms authentication in Chapter 18.

Generic

Provides a generic role-based security implementation that is independent of any specific authentication and authorization mechanism. See Section 10.2.5 for details.

Passport

Provides a role-based authentication mechanism that relies on the Microsoft Passport .NET web-based service to authenticate users. Passport authentication only provides an implementation of IIdentity. A discussion of Passport authentication is beyond the scope of this book, and we refer you to the .NET Framework SDK and Passport SDK documentation for further details.

Windows

Provides a role-based security implementation that is based on the users defined in the Windows user account system. See Section 10.2.3 for details.

We describe the members of the IIdentity and IPrincipal interfaces in Table 10-1. In later sections, when we discuss the Windows and Generic implementations of these interfaces, we highlight any implementation-specific behavior.

Table 10-1. Members of the IIdentity and IPrincipal interfaces

Member

Description

IIdentity interface

 

AuthenticationType

Property that returns a string specifying the type of authentication used to identify the user represented by the IIdentity object

IsAuthenticated

Property that returns true if the user represented by the IIdentity has been authenticated; otherwise it returns false

Name

Property that returns a string containing the name of the user represented by the IIdentity object

IPrincipal interface

 

Identity

Property that returns the IIdentity object contained in the IPrincipal object

IsInRole

Method that returns true if the IIdentity contained in the IPrincipal is a member of the role with the specified name; otherwise, it returns false

The IPrincipal interface makes no provision for enumerating or accessing all of a principal's roles. The only access provided is to test individual role names against the principal's role set using the IsInRole method.

10.2.2 Determining the Current Principal

Every thread executing in the .NET runtime has an IPrincipal object associated with it. The thread's IPrincipal object represents the user on whose behalf the thread is running, and allows the runtime to make role-based security decisions for the thread based on the user's identity and roles. However, many applications do not use the RBS features of .NET, and so in the interest of performance and conserving resources, the .NET runtime does not automatically assign an IPrincipal object to every thread. If you intend to use RBS, you must either assign an IPrincipal to a thread manually or configure the runtime to create one automatically the first time it is needed.

You can use the System.AppDomain.SetThreadPrincipal method shown in the following code to specify an IPrincipal object that the runtime will automatically assign to each thread running in the application domain. You can call the SetThreadPrincipal method only once on each application domain; otherwise, an instance of System.Security.Policy.PolicyException is thrown:

# C#

public void SetThreadPrincipal(
    IPrincipal principal
);

# Visual Basic .NET

NotOverridable Public Sub SetThreadPrincipal( _
    ByVal principal As IPrincipal _
)

Instead of using SetThreadPrincipal to set a default IPrincipal for an entire application domain, you can set the current thread's IPrincipal manually through the System.Threading.Thread.CurrentPrincipal property. CurrentPrincipal is static (C#) or Shared (Visual Basic .NET) and always affects the IPrincipal of the currently executing thread. You also use CurrentPrincipal to obtain the IPrincipal of the current thread.

Code must have the ControlPrincipal permission of the System.Security.Permissions.SecurityPermission class in order to call the AppDomain.SetThreadPrincipal method or set the Thread.CurrentPrincipal property. We discuss the SecurityPermission class in Chapter 7.

If you do not use AppDomain.SetThreadPrincipal or Thread.CurrentPrincipal to assign an IPrincipal to a Thread, the principal policy of the application domain in which the thread is running determines what happens when code tries to obtain the thread's CurrentPrincipal. You can configure the principal policy of an application domain using the System.AppDomain.SetPrincipalPolicy method, which has the following signature:

# C#

public void SetPrincipalPolicy(
    PrincipalPolicy policy
);

# Visual Basic .NET

NotOverridable Public Sub SetPrincipalPolicy( _
    ByVal policy As PrincipalPolicy _
)

The policy argument is a member of the System.Security.Principal.PrincipalPolicy enumeration, whose values we list in Table 10-2. The default principal policy for all application domains is UnauthenticatedPrincipal, which creates an empty IPrincipal that is not useful for making role-based security decisions.

To call SetPrincipalPolicy, code must have the ControlPrincipal permission of the System.Security.Permissions.SecurityPermission class.

Table 10-2. Members of the PrincipalPolicy enumeration

Value

Description

NoPrincipal

No principal or identity object is created. Getting the Thread.CurrentPrincipal property will return null (C#) or Nothing (Visual Basic .NET). All role-based security demands will fail.

UnauthenticatedPrincipal

The runtime creates a GenericPrincipal containing a GenericIdentity with its Name and AuthenticationType properties set to empty strings (""), and its IsAuthenticated property set to false. The GenericIdentity is a member of no roles. We discuss the GenericIdentity and GenericPrincipal classes in Section 10.2.3 later in this chapter.

WindowsPrincipal

The runtime creates a WindowsPrincipal containing a WindowsIdentity based on the Windows access token of the current thread. The roles contained in the WindowsPrincipal are the Windows groups of the current user. We discuss the WindowsIdentity and WindowsPrincipal classes in Section 10.2.3 later in this chapter.

As you can see from the three principal policy options, if you want to use an RBS implementation in your programs that does not rely the current Windows user, principal policy provides no benefit. It is your responsibility to ensure that the current thread has the correct IPrincipal associated with it. You must create the IPrincipal object and assign it to the thread using the AppDomain.SetThreadPrincipal method or the Thread.CurrentPrincipal property.

10.2.3 Programming the Windows Role-Based Security Implementation

The WindowsIdentity and WindowsPrincipal classes of the System.Security.Principal namespace provide an RBS implementation that allows you to base security decisions on the identity and roles of Windows user accounts. The WindowsIdentity class implements the IIdentity interface and represents a Windows user account. The Windows user's name, accessible through the WindowsIdentity.Name property, is of the form "Domain\User."

The WindowsPrincipal class implements IPrincipal and contains the user's WindowsIdentity object, along with the names of the Windows groups to which the user belongs. The name of any built-in Windows user groups has the prefix "BUILTIN\"—for example, "BUILTIN\Administrators" or "BUILTIN\Users". This is important to remember when using the WindowsPrincipal.IsInRole method to test role membership. Non-built-in group names are prefixed with the name of the domain to which the group belongs, or the machine name if the group exists on a standalone machine—for example, "MyDomain\Developers" or "MyMachine\Developers".

The WindowsIdentity and WindowsPrincipal do not provide access to the Windows operating system permissions granted to the user and groups they represent. You must base your RBS decisions solely on the username and the names of the groups to which the user belongs.

The WindowsIdentity class implements members in addition to those defined in the IIdentity interface. These additional members provide useful functionality for creating WindowsIdentity objects, testing what type of Windows account a WindowsIdentity object represents, and impersonating Windows users. We summarize the members of the WindowsIdentity class in Table 10-3.

Table 10-3. Members of the WindowsIdentity class

Member

Description

Properties

 

AuthenticationType

Defined in IIdentity. Returns a string identifying the mechanism used to authenticate the user represented by the WindowsIdentity object.

IsAnonymous

Returns true if the Windows account type represented by the WindowsIdentity object is an anonymous account. Normally, you will see only anonymous identities within ASP.NET applications; see Chapter 18 for a discussion of ASP.NET security.

IsAuthenticated

Defined in IIdentity. Returns true of the user represented by the WindowsIdentity object was authenticated; otherwise, it returns false.

IsGuest

Returns true if the Windows account type represented by the WindowsIdentity object is a guest account.

IsSystem

Returns true if the Windows account type represented by the WindowsIdentity object is a system account.

Name

Defined in IIdentity. Returns the Windows logon name of the user. The Name property returns a string of the form "Domain\User."

Token

Returns a System.IntPtr containing the handle of the Windows account token for the user represented by the WindowsIdentity object.

Methods

 

GetAnonymous

Static method that returns a WindowsIdentity object that represents an anonymous Windows user account.

GetCurrent

Static method that returns a WindowsIdentity object that represents the currently logged-on Windows user account.

Impersonate

Allows code to impersonate other Windows users. See "Impersonating Windows Users."

The WindowsPrincipal class implements no role-based functionality other than that specified by the IPrincipal interface. The Identity property returns the contained WindowsIdentity as an IIdentity object; therefore, you must cast it to WindowsIdentity before you can use the additional methods listed in Table 10-3.

Of note is the implementation of the IsInRole method, which has three overloads:

# C#

public virtual bool IsInRole(
    int rid
);

public virtual bool IsInRole(
    string role
);

public virtual bool IsInRole(
    WindowsBuiltInRole role
);

# Visual Basic .NET

Overridable Overloads Public Function IsInRole( _
    ByVal rid As Integer _
) As Boolean

Overridable Overloads Public Function IsInRole( _
    ByVal role As String _
) As Boolean

Overridable Overloads Public Function IsInRole( _
    ByVal role As WindowsBuiltInRole _
) As Boolean

In the first overload, the rid argument specifies a Windows Role Identifier (RID). RIDs are a component of a Windows group's security identifier and provide a way to identify groups independent of language localization. The second overload takes a case-insensitive string that specifies the name for which to test; this is the standard form of IsInRole defined in IPrincipal. The final overload takes a member of the System.Security.Principal.WindowsBuiltInRole enumeration as an argument; WindowsBuiltInRole contains values that represent the standard Windows groups. Table 10-4 compares the role names, RIDs, and WindowsBuiltInRole values used to test for membership of the most commonly used Windows groups.

Table 10-4. Commonly used role names, RIDs, and WindowsBuiltInRole members

Name

RID (hex)

WindowsBuiltInRole

BUILTIN\Account Operators

0x224

AccountOperator

BUILTIN\Administrators

0x220

Administrator

BUILTIN\Backup Operators

0x227

BackupOperator

BUILTIN\Guests

0x222

Guest

BUILTIN\Power Users

0x223

PowerUser

BUILTIN\Print Operators

0x226

PrintOperator

BUILTIN\Replicator

0x228

Replicator

BUILTIN\Server Operators

0x225

SystemOperator

BUILTIN\Users

0x221

User

10.2.3.1 Configuring the current WindowsPrincipal

The easiest way to make the current principal represent the active Windows user is by calling the System.AppDomain.SetPrincipalPolicy method and passing it the value PrincipalPolicy.WindowsPrincipal. The first time code requests the thread's IPrincipal, the runtime creates a WindowsIdentity and a WindowsPrincipal for a thread based on the currently active Windows access token:

# C#

// Configure the current application domain's principal policy
// to represent the active Windows user
AppDomain.CurrentDomain.SetPrincipalPolicy(
    PrincipalPolicy.WindowsPrincipal);

# Visual Basic .NET

' Configure the current application domain's principal policy
' to represent the active Windows user
AppDomain.CurrentDomain.SetPrincipalPolicy( _
PrincipalPolicy.WindowsPrincipal)

You can also create WindowsIdentity and WindowsPrincipal objects manually and pass them to the AppDomain.SetThreadPrincipal method or the Thread.CurrentPrincipal property. The AppDomain.SetThreadPrincipal defines the default principal that the runtime assigns to any thread in the application domain, whereas the Thread.CurrentPrincipal property sets the principal of the current thread.

The WindowsIdentity.GetCurrent method returns a WindowsIdentity object that represents the current Windows user. Once you have a WindowsIdentity object, you can pass it to the WindowsPrincipal constructor and assign the new WindowsPrincipal to the current thread, as demonstrated by the following statements:

# C#

// Create a WindowsIdentity for the active Windows user
WindowsIdentity wi = WindowsIdentity.GetCurrent(  );

// Create a new WindowsPrincipal
WindowsPrincipal wp = new WindowsPrincipal(wi);

// Assign the WindowsPrincipal to the active thread.
Thread.CurrentPrincipal = wp;

# Visual Basic .NET

' Create a WindowsIdentity for the active Windows user
Dim wi As WindowsIdentity =  WindowsIdentity.GetCurrent(  ) 

' Create a new WindowsPrincipal
Dim wp As WindowsPrincipal =  New WindowsPrincipal(wi) 

' Assign the WindowsPrincipal to the active thread.
Thread.CurrentPrincipal = wp

Using the Thread.CurrentPrincipal property or the AppDomain.SetThreadPrincipal method allows you to change the thread's current principal if you need your code to operate on behalf of a different principal—a process known as impersonation. However, changing the current principal does not change the current Windows access token.

10.2.3.2 Impersonating Windows users

Sometimes, you will want your code to act at the operating system level as though it is a different user than the one currently logged on. This is particularly common in server applications that process requests from different users and need to access resources, such as databases, on behalf of the user.

The WindowsIdentity class provides the mechanism through which you can impersonate another Windows user. However, first you must obtain a Windows access token that represents the user you want to impersonate. Unfortunately, the .NET Framework class library does not currently contain classes that provide managed access to the Windows account database, and therefore you must call the LogonUser method of the unmanaged advapi32.dll Win32 library to obtain the access token. The LogonUser method takes username and password arguments, along with other parameters that control the authentication process, and provides access to an access token for the user. If the LogonUser fails, you will also need to call the System.Runtime.InteropServices.Marshal.GetLastWin32Error method to determine what the problem is. The GetLastWin32Error method exposes the GetLastError method of the kernel32.dll library and avoids possible problems arising from the CLR making internal calls to the WIN32 APIs that would overwrite the last error code.

On the Windows NT and Windows 2000 platforms, the account under which a program is running requires the Windows SE_TCB_NAME privilege to call LogonUser in order to obtain an access token. Without this privilege, calls to LogonUser fail and calling GetLastError returns the code ERROR_PRIVILEGE_NOT_HELD (value 1314). To grant an account the SE_TCB_NAME privilege, you must configure the local security policy to allow the account to "Act as part of the operating system." This highly trusted privilege allows the account to create access tokens that represent any user or permission set. You should only grant this permission to accounts created specifically to run your software; never grant this permission to user accounts.

Once you have the access token, you use it to create a new WindowsIdentity object and call its Impersonate method. The Impersonate method changes the Windows access token of the current thread. Any operating system operation you perform after calling Impersonate takes place as the impersonated user. The Impersonate method returns a System.Security.Principal.WindowsImpersonationContext object that represents the Windows user prior to impersonation. When you want to revert to the original user, call the WindowsImpersonationContext.Undo method.

Example 10-1 demonstrates the impersonation of a Windows user named "Bob." Although the example demonstrates how to call LogonUser from managed code, a complete discussion of unmanaged code interoperability and the LogonUser method is beyond the scope of this book. You should consult the .NET Framework SDK documentation for details on calling unmanaged code and the Windows Platform SDK for details of the LogonUser method.

Example 10-1. Impersonating a Windows user
# C#

using System;
using System.IO;
using System.Security.Principal;
using System.Security.Permissions;
using System.Runtime.InteropServices;

// Make sure we have permission to execute unmanged code
[assembly:SecurityPermission(SecurityAction.RequestMinimum,
    UnmanagedCode=true)]

public class WindowsImpersonationTest {
        
        // Define the external LogonUser method from advapi32.dll.
    [DllImport("advapi32.dll", SetLastError=true)]
    static extern int LogonUser(String UserName, String Domain, 
        String Password, int LogonType, int LogonProvider,
        ref IntPtr Token);

    public static void Main(  ) {

        // Create a new initialized IntPtr to hold the access token
        // of the user to impersonate.
        IntPtr token = IntPtr.Zero;
        
        // Call LogonUser to obtain an access token for the user
        // "Bob" with the password "treasure". We authenticate against
        // the local accounts database by specifying a "." as the Domain
        // argument.
        int ret = LogonUser(@"Bob", ".", "treasure", 2, 0, ref token);
        
        // If the LogonUser return code is zero an error has occured. 
        // Display it and exit.
        if (ret == 0)  {       
            Console.WriteLine("Error {0} occured in LogonUser", 
                Marshal.GetLastWin32Error(  ));
            return;
        }
       
        // Create a new WindowsIdentity from Bob's access token
        WindowsIdentity wi = new WindowsIdentity(token);
    
        // Impersonate Bob, saving a reference to the returned 
        // WindowsImpersonationContext.
        WindowsImpersonationContext impctx = wi.Impersonate(  );
        
        // !!! Perform actions as Bob !!!
        // We create a file that Windows will show is owned by Bob.
        StreamWriter file = new StreamWriter("test.txt");
        file.WriteLine("Bob's test file.");
        file.Close(  );
        
        // Revert back to the original Windows user using the 
        // WindowsImpersonationContext object.
        impctx.Undo(  );
        }
}

# Visual Basic .NET

Imports System
Imports System.IO
Imports System.Security.Principal
Imports System.Security.Permissions
Imports System.Runtime.InteropServices
 
' Make sure we have permission to execute unmanged code
<assembly:SecurityPermission(SecurityAction.RequestMinimum, _
UnmanagedCode:=True)> _ 
 
Public Class WindowsImpersonationTest
 
        ' Define the external LogonUser method from advapi32.dll.
        <DllImport("advapi32.dll", SetLastError := True)> _
        Public Shared Function LogonUser(UserName As String, _
        Domain As String, Password As String, LogonType As Integer, _
        LogonProvider As Integer, ByRef Token As IntPtr) As Integer
        End Function
 
    Public Shared Sub Main(  )
 
        ' Create a new initialized IntPtr to hold the access token
        ' of the user to impersonate.
        Dim token As IntPtr = IntPtr.Zero 
 
        ' Call LogonUser to obtain an access token for the user
        ' "Bob" with the password "treasure". We authenticate against
        ' the local accounts database by specifying a "." as the Domain
        ' argument.
        Dim ret As Integer = LogonUser("Bob",".","treasure",2,0, token) 
 
        ' If the LogonUser return code is zero an error has occured. 
        ' Display it and exit.
        If ret = 0 Then
            Console.WriteLine("Error {0} occured in LogonUser", _
            Marshal.GetLastWin32Error(  ))
            Return
        End If
 
        ' Create a new WindowsIdentity from Bob's access token
        Dim wi As WindowsIdentity = New WindowsIdentity(token) 
 
        ' Impersonate Bob, saving a reference to the returned 
        ' WindowsImpersonationContext.
        Dim impctx As WindowsImpersonationContext = wi.Impersonate(  ) 
 
        ' !!! Perform actions as Bob !!!
        ' We create a file that Windows will show is owned by Bob.
        Dim file As StreamWriter = New StreamWriter("test.txt") 
        file.WriteLine("Bob's test file.")
        file.Close(  )
 
        ' Revert back to the original Windows user using the 
        ' WindowsImpersonationContext object.
        impctx.Undo(  )
    End Sub
End Class

10.2.4 Making Role-Based Security Demands

Role-based security demands function similarly to code-access security demands. The key difference is that role-based security demands never result in a stack walk. The result of a role-based security demand is based solely on the identity and roles of the active thread's principal. If the thread's principal does not contain the demanded identity or roles, the runtime throws a System.Security.SecurityException. If the principal meets the demanded requirements, execution continues unaffected.

The PrincipalPermission class, with its attribute counterpart named PrincipalPermissionAttribute, provides the mechanisms through which you invoke role-based security demands in your programs. The PrincipalPermission class allows you to use imperative security syntax, and the PrincipalPermissionAttribute class enables the use of declarative syntax; both classes are members of the System.Security.Permissions namespace. We discussed the syntax and use of both imperative and declarative security demands in Chapter 7, and therefore we will only cover them briefly in the following sections.

Unlike code-access security, which is enforced exclusively through permissions and attributes, it is commonplace to make role-based security decisions by calling the members of a thread's principal and identity objects directly. This is particularly useful if the RBS implementation includes functionality in addition to that defined in the IIdentity or IPrincipal interfaces. For example, the implementation may expose features specific to the underlying authentication or authorization mechanism, as is the case with the WindowsIdentity object that we discussed in Section 10.2.3.

10.2.4.1 Using imperative role-based security statements

To make an imperative RBS demand, you must instantiate a PrincipalPermission object and call its Demand method. The most commonly used PrincipalPermission constructor has the signature shown here:

# C#

public PrincipalPermission(
    string name,
    string role
);

# Visual Basic .NET

Public Sub New( _
    ByVal name As String, _
    ByVal role As String _
)

The name and role arguments specify the values that the current principal must have for the Demand to succeed. Each PrincipalPermission can specify only a single role name. The current principal must match both the specified name and role values; however, you can specify null (C#) or Nothing (Visual Basic .NET) for either argument to match any value. Table 10-5 explains how PrincipalPermission uses these arguments to in determine the result of the Demand.

Table 10-5. Arguments of the PrincipalPermission constructors

Argument

Description

name

Must match the value held in the Thread.CurrentPrincipal.Identity.Name property. Both the GenericPrincipal and WindowsPrincipal classes implement case-insensitive comparisons of the Name property.

role

Used as an argument to the Thread.CurrentPrincipal.IPrincipal.IsInRole method to test if the current principal is a member of the specified role. Both the GenericPrincipal and WindowsPrincipal classes implement case-insensitive comparisons for IsInRole.

The following statements demonstrate the use of the PrincipalPermission class to invoke a variety of imperative RBS demands:

# C#

// Demand that the active principal has the identity name "Peter".
PrincipalPermission p1 = new PrincipalPermission("Peter", null);
p1.Demand(  );

// Demand that the active principal is a member of the "Developers"
// group with any identity.
PrincipalPermission p2 = new PrincipalPermission(null,"Developers");
p2.Demand(  );

// Demand that the active principal have the identity name "Bart" 
// and be a member of the "Managers" group.
PrincipalPermission p3 = new PrincipalPermission("Bart", "Managers");
p3.Demand(  );

# Visual Basic .NET

' Demand that the active principal has the identity name "Peter".
Dim p1 As PrincipalPermission = _
New PrincipalPermission("Peter",Nothing) 
p1.Demand(  )
 
' Demand that the active principal is a member of the "Developers"
' group with any identity.
Dim p2 As PrincipalPermission = _
New PrincipalPermission(Nothing,"Developers") 
p2.Demand(  )
 
' Demand that the active principal have the identity name "Bart" 
' and be a member of the "Managers" group.
Dim p3 As PrincipalPermission = _
New PrincipalPermission("Bart","Managers") 
p3.Demand(  )

PrincipalPermission implements the System.Security.IPermission interface, and defines the Copy, Intersect, IsSubsetOf, and Union methods to manipulate PrincipalPermission objects. The composition of PrincipalPermission objects means the Intersect and IsSubSetOf methods are of little value; PrincipalPermission primarily implements them to satisfy the requirements of the IPermission interface. The Union method, however, does provide useful functionality, enabling you to make security demands that test against multiple sets of name/role pairs.

The Union of two PrincipalPermission objects is a PrincipalPermission object that contains the name and role elements of both source objects. For example if the two source PrincipalPermission objects contained the name/role values "Alice"/"managers" and "Bob"/"developers," the union would contain both name/role pairs. Calling Demand on the resulting PrincipalPermission would succeed if the current principal matches either name/role pair. However, PrincipalPermission maintains and compares the contained name/role pairs independently. Comparing the union against a principal with the name/role pair "Alice"/"developers" would fail. To understand the contents of a PrincipalPermission resulting from a Union, look at the following output from the PrincipalPermission.ToString method:

<Permission class="System.Security.Permissions.PrincipalPermission,
mscorlib, Version=1.0.5000.0, Culture=neutral, 
PublicKeyToken=b77a5c561934e089"
            version="1">
   <Identity Authenticated="true"
             ID="Alice"
             Role="managers"/>
   <Identity Authenticated="true"
             ID="Bob"
             Role="developers"/>
</Permission>
10.2.4.2 Using declarative role-based security statements

You can apply PrincipalPermissionAttribute to classes, methods, properties, or events to force declarative security demands. The key difference between PrincipalPermissionAttribute and the permission attributes (see Chapter 7) is that you cannot apply the PrincipalPermisisonAttribute at the assembly level. This means you cannot make role-based permission requests, such as RequestMinimum, RequestOptional, and RequestRefuse. In addition, because RBS does not use or affect the call stack, there are no declarative stack override statements available. Thus, you can only invoke the following three role-based security statements using declarative syntax: Demand, LinkDemand, and InheritenceDemand.

The PrincipalPermissionAttribute class defines the Name and Role properties that you use to specify the name and role values that the current principal must have for the declarative RBS demand to succeed. The following statements demonstrate the equivalent PrincipalPermissionAttribute syntax used to make the same demands we made in the previous section:

# C#

// Demand that the active principal has the identity name "Peter".
[PrincipalPermission(SecurityAction.Demand, Name = "Peter")]

// Demand that the active principal is a member of the "Developers"
// group with any identity.
[PrincipalPermission(SecurityAction.Demand, Role = "Developers")]

// Demand that the active principal have the identity name "Bart" 
// and be a member of the "Managers" group.
[PrincipalPermission(SecurityAction.Demand, Name = "Bart", Role = "Managers")]

# Visual Basic .NET

' Demand that the active principal has the identity name "Peter".
<PrincipalPermission(SecurityAction.Demand, Name := "Peter")> _ 
  
' Demand that the active principal is a member of the "Developers"
' group with any identity.
<PrincipalPermission(SecurityAction.Demand, Role := "Developers")> _ 

' Demand that the active principal have the identity name "Bart" 
' and be a member of the "Managers" group.
<PrincipalPermission(SecurityAction.Demand, Name:="Bart", Role:="Managers")> _

10.2.5 Programming the Generic Role-Based Security Implementation

If you rely on an authority other than the Windows accounts system to authenticate and authorize your users, it is possible that the manufacturers of the system will provide IIdentity and IPrincipal implementations that work directly with the authority. If not, the simplest approach is to obtain identity and role information from the custom authority and use the GenericIdentity and GenericPrincipal classes from the System.Security.Principal namespace to enable role-based security support in your managed applications. The GenericIdentity and GenericPrincipal classes provide a generic RBS implementation that you can use independently of the Windows user accounts database. The GenericIdentity class implements IIdentity, and GenericPrincipal implements IPrincipal.

10.2.5.1 Configuring the current GenericPrincipal

The techniques for setting the current principal to a GenericPrincipal object are more limited than those we described earlier in Section 10.2.3.1. It is impossible for an application domain's principal policy to create useful GenericPrincipal objects automatically; therefore, you have two options:

  • Create a GenericPrincipal object and pass it to the AppDomain.SetThreadPrincipal method. This defines the default principal that the runtime will assign to any thread executing in the application domain.

  • Create a GenericPrincipal object and use it to set the Thread.CurrentPrincipal property. This sets the principal of the current thread.

To create a GenericPrincipal, you must first create a GenericIdentity. The GenericIdentity class provides two constructors with the following signatures:

# C#

public GenericIdentity(
    string name
);

public GenericIdentity(
    string name,
    string type
);
 
# Visual Basic .NET

Public Sub New( _
    ByVal name As String _
)

Public Sub New( _
    ByVal name As String, _
    ByVal type As String _
)

The first constructor takes a single name argument, in which you specify the name of the user represented by the identity. In addition to the name argument, the second constructor takes a type argument. The type argument allows you to specify a string that identifies the type of authentication mechanism used to authenticate the user. The GenericIdentity class enforces no restrictions on the format or content of the name and type arguments as long as they contain only valid string characters. This provides maximum flexibility, which allows you to use the GenericIdentity class in conjunction with any custom authentication mechanisms. The following statements demonstrate the use of the GenericIdentity constructors:

# C#

GenericIdentity i1 = new GenericIdentity("Peter");
GenericIdentity i2 = new GenericIdentity("Peter","SmartCard");

# Visual Basic .NET

Dim i1 As GenericIdentity = New GenericIdentity("Peter") 
Dim i2 As GenericIdentity = New GenericIdentity("Peter","SmartCard")

The GenericPrincipal class provides a single constructor that takes an IIdentity object and a string array as arguments:

# C#

public GenericPrincipal(
    IIdentity identity,
    string[] roles
);

# Visual Basic .NET

Public Sub New( _
    ByVal identity As IIdentity, _
    ByVal roles(  ) As String _
)

The identity argument can be of any type that implements IIdentity and represents the user that the GenericPrincipal should represent. The roles argument takes a string array containing the set of role names to which the user belongs. You must specify the user roles in the GenericPrincipal object constructor because the GenericPrincipal class is independent of any underlying user-authorization mechanism. The following statements demonstrate the creation of a GenericPrincipal from a GenericIdentity, and then make the new GenericPrincipal the current principal using the Thread.CurrentPrincipal property:

# C#

// Create a GenericIdentity for the user Peter
GenericIdentity gi = new GenericIdentity("Peter");

// Create a GenericPrincipal for Peter and specify membership of the
// Developers and Managers roles
String[] roles = new String[]{"Developers", "Managers"};
GenericPrincipal gp = new GenericPrincipal(gi, roles);

// Assign the new principal to the current thread
Thread.CurrentPrincipal = gp;

# Visual Basic .NET

' Create a GenericIdentity for the user Peter
Dim gi As GenericIdentity = New GenericIdentity("Peter") 
 
' Create a GenericPrincipal for Peter and specify membership of the
' Developers and Managers roles
Dim roles(  ) As String = New String(  ) {"Developers", "Managers"}
Dim gp As GenericPrincipal = New GenericPrincipal(gi,roles)

' Assign the new principal to the current thread
Thread.CurrentPrincipal = gp;

Both the GenericIdentity and GenericPrincipal classes are simple, offering no role-based functionality other than that defined in their respective IIdentity and IPrincipal interfaces. In Table 10-6, we describe the GenericIdentity and GenericPrincipal classes' implementations of the IIdentity and IPrincipal interfaces.

Table 10-6. Members of GenericIdentity and GenericPrincipal classes

Member

Description

GenericIdentity class

 

AuthenticationType

Read-only property that gets the authentication type specified in the type argument of the GenericIdentity constructor. If type was not specified, an empty string is returned.

IsAuthenticated

Read-only property that returns true as long as the username specified in the name argument of the GenericIdentity constructor was not an empty string; otherwise, it returns false.

Name

Read-only property that gets the username specified in the name argument of the GenericIdentity constructor.

GenericPrincipal class

 

Identity

Read-only property that gets the IIdentity object specified in the identity argument of the GenericPrincipal constructor. You must cast it to the right type, which will normally be GenericIdentity.

IsInRole

Method that takes a string argument containing the name of a role and returns true if the specified role name matches one of those contained in the roles argument of the GenericPrincipal constructor. The comparison is case-insensitive.

    [ Team LiB ] Previous Section Next Section