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.
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;
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 1. In later sections, when we discuss the
Windows and Generic implementations of these interfaces, we highlight
any implementation-specific behavior.
Table 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.
|
|
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.
|
|
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 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.
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 3.
Table 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;
|
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 4 compares the role names, RIDs, and
WindowsBuiltInRole values used to test for
membership of the most commonly used Windows groups.
Table 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
|
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.
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 ImpersonateSystem.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. method
returns a
Example 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 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
|
.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.
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.
|
|
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 5 explains how
PrincipalPermission uses these arguments to in
determine the result of the Demand.
Table 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; PrincipalPermissionIPermission interface. The
Union method, however, does provide useful
functionality, enabling you to make security demands that test
against multiple sets of name/role pairs.
primarily implements them to satisfy the requirements of the
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>
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 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")> _
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.
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 Table 6, we describe the
GenericIdentity and
GenericPrincipal classes'
implementations of the
IIdentity and IPrincipal
interfaces.
interfaces. In
Table 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.
|