Introduction Role providers provide the
interface between Microsoft ASP.NET's role management service (the
"role manager") and role data sources. ASP.NET 2.0 ships with three
role providers:
- SqlRoleProvider, which stores role data in Microsoft SQL Server and Microsoft SQL Server Express databases
- AuthorizationStoreRoleProvider, which retrieves role information from Microsoft Authorization Manager ("AzMan")
- WindowTokenRoleProvider,
which retrieves role information from each user's Microsoft Windows
authentication token, and returns his or her group membership(s).
The
fundamental job of a role provider is to interface with data sources
containing containing role data mapping users to roles, and to provide
methods for creating roles, deleting roles, adding users to roles, and
so on. The Microsoft .NET Framework's System.Web.Security namespace includes a class named RoleProvider that defines the basic characteristics of a role provider. RoleProvider is prototyped as follows: public abstract class RoleProvider : ProviderBase { // Abstract properties public abstract string ApplicationName { get; set; }
// Abstract methods public abstract bool IsUserInRole (string username, string roleName); public abstract string[] GetRolesForUser (string username); public abstract void CreateRole (string roleName); public abstract bool DeleteRole (string roleName, bool throwOnPopulatedRole); public abstract bool RoleExists (string roleName); public abstract void AddUsersToRoles (string[] usernames, string[] roleNames); public abstract void RemoveUsersFromRoles (string[] usernames, string[] roleNames); public abstract string[] GetUsersInRole (string roleName); public abstract string[] GetAllRoles (); public abstract string[] FindUsersInRole (string roleName, string usernameToMatch); }
The following sections document the implementation of SqlRoleProvider and AuthorizationStoreRoleProvider, both of which derive from RoleProvider. SqlRoleProvider SqlRoleProvider is the Microsoft role provider for SQL Server databases. It stores role data, using the schema documented in "Data Schema," and it uses the stored procedures documented in "Data Access." All knowledge of the database schema is hidden in the stored procedures, so that porting SqlRoleProvider
to other database types requires little more than modifying the stored
procedures. (Depending on the targeted database type, the ADO.NET code
used to call the stored procedures might have to change, too. The
Microsoft Oracle .NET provider, for example, uses a different syntax
for named parameters.) The ultimate reference for SqlRoleProvider is the SqlRoleProvider source code, which is found in SqlRoleProvider.cs. The sections that follow highlight key aspects of SqlRoleProvider's design and operation. Provider Initialization Initialization occurs in SqlRoleProvider.Initialize, which is called one time—when the provider is loaded—by ASP.NET. SqlRoleProvider.Initialize processes the configuration attributes applicationName, connectionStringName, and commandTimeout, and throws a ProviderException exception if unrecognized configuration attributes remain. SqlRoleProvider.Initialize also reads the connection string identified by the connectionStringName attribute from the <connectionStrings> configuration section, and caches it in a private field. It throws a ProviderException if the attribute is empty or nonexistent, or if the attribute references a nonexistent connection string. Data Schema SqlRoleProvider
persists roles in the aspnet_Roles table of the provider database. Each
record in aspnet_Roles corresponds to one role. The ApplicationId
column refers to the column of the same name in the aspnet_Applications
table, and it is used to scope roles by application. Table 3-1
documents the aspnet_Roles table's schema. Table 3-1. The aspnet_Roles table | Column Name | Column Type | Description | | ApplicationId | uniqueidentifier | Application ID | | RoleId | uniqueidentifier | Role ID | | RoleName | nvarchar(256) | Role name | | LoweredRoleName | nvarchar(256) | Role name (lowercase) | | Description | nvarchar(256) | Role description (currently unused) | SqlRoleProvider
uses a separate table named aspnet_UsersInRoles to map roles to users.
The UserId column identifies a user in the aspnet_Users table, whereas
the RoleId column identifies a role in the aspnet_Roles table. The
structure of this table, which is documented in Table 3-2, lends itself
to the types of queries performed by a role provider. With a single
query, for example, a role provider could select all the users
belonging to a given role, or all the roles assigned to a given user. Table 3-2. The aspnet_UsersInRoles table | Column Name | Column Type | Description | | UserId | uniqueidentifier | User ID | | RoleId | uniqueidentifier | Role ID | Scoping of Role Data Websites that register role providers with identical applicationName attributes share role data, whereas websites that register role providers with unique applicationNames do not. To that end, SqlRoleProvider records an application ID in the ApplicationId field of each record in the aspnet_Roles table. aspnet_Roles' ApplicationId field refers to the field of the same name in the aspnet_Applications table, and each unique applicationName has a corresponding ApplicationId in that table. Data Access SqlRoleProvider performs all database accesses through stored procedures. Table 3-3 lists the stored procedures that it uses. Table 3-3. Stored procedures used by SqlRoleProvider | Stored Procedure | Description | | aspnet_Roles_CreateRole | Adds a role to the aspnet_Roles table and, if necessary, adds a new application to the aspnet_Applications table. | | aspnet_Roles_DeleteRole | Removes
a role from the aspnet_Roles table. Optionally deletes records
referencing the deleted role from the aspnet_UsersInRoles table. | | aspnet_Roles_GetAllRoles | Retrieves all roles with the specified application ID from the aspnet_Roles table. | | aspnet_Roles_RoleExists | Checks the aspnet_Roles table to determine whether the specified role exists. | | aspnet_UsersInRoles_AddUsersToRoles | Adds the specified users to the specified roles by adding them to the aspnet_UsersInRoles table. | | aspnet_UsersInRoles_FindUsersInRole | Queries
the aspnet_UsersInRoles table for all users belonging to the specified
role whose user names match the specified pattern. | | aspnet_UsersInRoles_GetRolesForUser | Queries the aspnet_UsersInRoles table for all roles assigned to a specified user. | | aspnet_UsersInRoles_GetUsersInRoles | Queries the aspnet_UsersInRoles table for all users belonging to the specified role. | | aspnet_UsersInRoles_IsUserInRole | Checks the aspnet_UsersInRoles table to determine whether the specified user belongs to the specified role. | | aspnet_UsersInRoles_RemoveUsersFromRoles | Removes the specified users from the specified roles by deleting the corresponding records from the aspnet_UsersInRoles table. | Stored procedure names are generally indicative of the SqlRoleProvider methods that call them. For example, applications call the role manager's Roles.CreateRole method to create new roles. Roles.CreateRole, in turn, delegates to the CreateRole method of the default role provider, which, in the case of SqlRoleProvider, validates the input parameters, and calls aspnet_Roles_CreateRole to create a new role. Creating Roles SqlRoleProvider.CreateRole calls the stored procedure aspnet_Roles_CreateRole, which performs the following tasks:
- Calls aspnet_Applications_CreateApplication to retrieve an application ID (or create a new one).
- Verifies that the specified role doesn't already exist—that is, that it's not already defined in the aspnet_Roles table.
- Inserts a record representing the new role into the aspnet_Roles table.
aspnet_Roles_CreateRole
performs all these steps within a transaction to ensure that changes
are committed as a group or not at all. Deleting Roles Applications call the role manager's Roles.DeleteRole method to delete roles. Roles.DeleteRole calls the default role provider's DeleteRole method, and passes in a flag named throwOnPopulatedRole that indicates whether DeleteRole should throw an exception if the role being deleted isn't empty—that is, if one or more users are assigned to it. SqlRoleProvider.DeleteRole calls the stored procedure aspnet_Roles_DeleteRole. The stored procedure performs the following tasks:
- Verifies that the role to be deleted exists.
- If throwOnPopulatedRole is true,
checks the aspnet_UsersInRoles table for records containing the
specified role ID, and returns an error code if the query turns up one
or more records.
- Deletes all records containing the specified role ID from the aspnet_UsersInRole table.
- Deletes all records containing the specified role ID from the aspnet_Roles table.
aspnet_Roles_DeleteRole
performs all these steps within a transaction to ensure that changes
are committed as a group or not at all. Adding Users to Roles Applications call the role manager's Roles.AddUserToRole, Roles.AddUserToRoles, Roles.AddUsersToRole, or Roles.AddUsersToRoles method to add users to roles. These methods, in turn, call the default role provider's AddUsersToRoles method. SqlRoleProvider.AddUsersToRoles
converts the arrays of user names and role names in the parameter list
into comma-delimited lists, and passes them to the stored procedure
aspnet_UsersInRoles_AddUsersToRoles. aspnet_UsersInRoles_AddUsersToRoles
validates the user names and role names passed to it, by verifying
their presence in the aspnet_Users and aspnet_Roles tables. Then, it
adds the specified users to the specified roles, by inserting one
record into the aspnet_UsersInRoles table for each user name/role name
pair passed to it. Removing Users from Roles Applications call the role manager's Roles.RemoveUserFromRole, Roles.RemoveUserFromRoles, Roles.RemoveUsersFromRole, or Roles.RemoveUsersFromRoles method to remove users from roles. These methods, in turn, call the default role provider's RemoveUsersFromRoles method. SqlRoleProvider.RemoveUsersFromRoles
converts the arrays of user names and role names in the parameter list
into comma-delimited lists, and passes them to the stored procedure
aspnet_UsersInRoles_RemoveUsersFromRoles. aspnet_UsersInRoles_RemoveUsersFromRoles
validates the user names and role names passed to it, by verifying
their presence in the aspnet_Users and aspnet_Roles tables. Then, it
removes the specified users from the specified roles, by deleting the
corresponding records from the aspnet_UsersInRoles table. Differences Between the Published Source Code and the .NET Framework's SqlRoleProvider The published source code for SqlRoleProvider
differs from the .NET Framework version in one respect: Declarative and
imperative CAS demands were commented out. Because the source code can
be compiled standalone, and thus will run as user code rather than
trusted code in the global assembly cache, the CAS demands are not
strictly necessary. For reference, however, the original demands from
the .NET Framework version of the provider have been retained as
comments. AuthorizationStoreRoleProvider AuthorizationStoreRoleProvider
is the Microsoft role provider for Microsoft Authorization Manager data
stores. Authorization Manager, also known as "AzMan," is a role-based
access control framework for Microsoft Windows applications. AuthorizationStoreRoleProvider maps AzMan roles to the ASP.NET role manager, and it is an alternative to SqlRoleProvider for organizations that don't wish to store role data in databases. Because AzMan integrates with Active Directory, AuthorizationStoreRoleProvider can be combined with ActiveDirectoryMembershipProvider to employ Active Directory as the source for both membership data and role data in ASP.NET applications. AuthorizationStoreRoleProvider
can also leverage AzMan's ability to store policy data in XML files and
Active Directory Application Mode (ADAM), a service introduced in
Microsoft Windows Server 2003 that supports application-specific views
of Active Directory. For an overview of AzMan's features and
capabilities, and how to use them from managed code, see "Use Role-Based Security in Your Middle Tier .NET Apps with Authorization Manager." AuthorizationStoreRoleProvider
doesn't support the full range of Authorization Manager features,
opting instead to support the subset of features that map directly to
the capabilities of the ASP.NET role manager. For example, AzMan allows
tasks such as "Approve purchase order" and "View salary history" to be
associated with roles, and it allows tasks to be subdivided into
operations such as "View purchase order" and "Sign purchase order." AuthorizationStoreRoleProvider
exposes AzMan roles as role-manager roles, but it does not expose (or
otherwise use) information regarding tasks and operations. The ultimate reference for AuthorizationStoreRoleProvider is the AuthorizationStoreRoleProvider source code, which is found in AuthStoreRoleProvider.cs. The sections that follow highlight key aspects of AuthorizationStoreRoleProvider's design and operation. AzMan Data Stores AuthorizationStoreRoleProvider
doesn't maintain data stores of its own, instead relying on AzMan data
stores, which can be administered with tools such as the AzMan MMC
snap-in or, if AuthorizationStoreRoleProvider is the default
role provider, the Web Site Administration Tool that comes with
ASP.NET. However, you can use the Web Site Adminstration Tool for role
management only if you use a membership provider (for example, ActiveDirectoryMembershipProvider) as well. The Web Site Administration Tool has no user interface for manipulating Windows user accounts directly. Authorization Manager can store data in XML files, Active Directory, or ADAM containers. AuthorizationStoreRoleProvider supports all three types of data stores. The connectionStringName configuration attribute tells AuthorizationStoreRoleProvider where AzMan data is stored. For example, the following connection string points AuthorizationStoreRoleProvider to an XML policy file: <connectionStrings> <add name="AzManConnectionString" connectionString="msxml://c:/websites/App_Data/roles/roles.xml" /> </connectionStrings>
By contrast, the following connection string points it to an ADAM container named Contoso, on a remote server named ORION: <connectionStrings> <add name="AzManConnectionString" connectionString="msldap://ORION/… CN=Contoso,OU=ContosoPartition,O=Contoso,C=US" /> </connectionStrings>
AuthorizationStoreRoleProvider is agnostic to the type of data store, because it uses the Authorization Manager API to read and write role data. Scoping of Role Data AzMan
data stores can be partitioned into applications and scopes, enabling
one data store to hold authorization data for multiple applications,
and one application to hold multiple sets of authorization settings. AuthorizationStoreRoleProvider supports both forms of scoping through its ApplicatioName and ScopeName properties. When AuthorizationStoreRoleProvider calls AzMan to open a data store, it passes in the application name and scope name stored in the ApplicationName and ScopeName properties, respectively. Thus, if the roles used by AuthorizationStoreRoleProvider are defined in an AzMan application named Contoso, and a scope named Roles, then matching applicationName and scopeName attributes should be included in the provider's configuration. If no application name is specified, AuthorizationStoreRoleProvider
uses a default application name, which for a Web application is the
application's virtual directory. Because AzMan doesn't allow
applications with names like /, you should always explicitly set the applicationName for AuthorizationStoreRoleProvider. If no scope name is specified, AuthorizationStoreRoleProvider doesn't use a scope name when opening the data store. AzMan Data Store Access AzMan's features are exposed to applications through unmanaged COM interfaces such as IAzAuthorizationStore, which represents AzMan data stores; IAzApplication, which represents AzMan applications; IAzScope, which represents AzMan scopes; and IAzRole, which represents AzMan roles. AuthorizationStoreRoleManager
uses COM interop and late binding to invoke AzMan methods exposed
through these interfaces. Invocation code is wrapped in helper methods
named CallMethod (reproduced in Listing 3-1) and CallProperty, which are used extensively by other AuthorizationStoreRoleProvider methods. For example, AuthorizationStoreRoleProvider.CreateRole uses the following code to call IAzScope.CreateRole or IAzApplication.CreateRole to create an AzMan role: object[] args = new object[2]; args[0] = roleName; args[1] = null; object role = CallMethod(_ObjAzScope != null ? _ObjAzScope : _ObjAzApplication, "CreateRole", args);
_objAzScope and _objAzApplication contain references to AzMan scope and application objects created when the provider was initialized. For details, refer to "Provider Initialization." Lsting 3-1. AuthorizationStoreRoleProvider's CallMethod method private object CallMethod(object objectToCallOn, string methodName, object[] args) { if( HostingEnvironment.IsHosted && _XmlFileName != null) { InternalSecurityPermissions.Unrestricted.Assert(); }
try { using (new ApplicationImpersonationContext()) { return objectToCallOn.GetType().InvokeMember(methodName, BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Instance, null, objectToCallOn, args, CultureInfo.InvariantCulture); } } catch { throw; } }
In Listing 3-1, note the provider's use of the internal ApplicationImpersonationContext
type. This ensures that the provider connects to the directory using
either the current process credentials, or the application
impersonation credentials if an explicit username and password were
specified in the <identity> element. The provider never
connects to the AzMan policy store by using the credentials of an end
user, even if user impersonation is enabled. Also, a quick note
on the unrestricted assert: The provider normally will not work in
partially trusted ASP.NET applications, because of its reliance on COM
interop to call unmanaged code. However, if your policy store is in an
XML file, then the provider relies on file I/O CAS permissions as a
surrogate security policy. This means you can use the provider in
partially trusted ASP.NET applications, provided that those
applications have CAS permissions to read the configured file path. Provider Initialization AuthorizationStoreRoleProvider initialization occurs in two stages. Stage 1 initialization occurs in AuthorizationStoreRoleProvider.Initialize, which is called one time—when the provider is loaded—by ASP.NET. AuthorizationStoreRoleProvider.Initialize processes the configuration attributes applicationName, scopeName, connectionStringName, and cacheRefreshInterval, and throws an exception if unrecognized configuration attributes remain. Stage 2 initialization is performed on a lazy, as-needed basis by a private AuthorizationStoreRoleProvider helper method named InitApp, which is called by CreateRole, DeleteRole, and other AuthorizationStoreRoleProvider methods prior to carrying out the operations they're tasked with. InitApp performs the following tasks:
- If the connection string referenced by connectionStringName contains an msxml:// prefix, InitApp
converts the path into a fully qualified file-system path, verifies
that the file exists, and verifies that the provider has permission to
access it. Then, it caches the path name in a private field.
- Uses reflection (through Activator.CreateInstance) to instantiate one of two versions of an internal class named Microsoft.Interop.Security.AzRoles.AzAuthorizationStoreClass representing AzMan. It instantiates version 1.2 of that class if it exists, or version 1.0 if it does not.
- Calls AzMan's IAzAuthorizationStore.Initialize method, passing in the connection string retrieved from the configuration.
- Calls AzMan's IAzAuthorizationStore.OpenApplication or IAzAuthorizationStore.OpenApplication2 method (depending on which version of AzAuthorizationStoreClass was loaded) with the application name stored in ApplicationName to open an AzMan application (and create an application object).
- If ScopeName is neither null nor empty, passes ScopeName to the IAzApplication.OpenScope method of the application object created in the previous step to open the specified scope (and create a scope object).
After InitApp has executed, AuthorizationStoreRoleProvider
is fully initialized and ready to do business. It caches references to
the AzAuthorizationStoreClass application, and to scope objects that it
created in private fields for use in subsequent method calls. If any of
its initialization steps fail, InitApp responds by throwing a ProviderException. Although AuthorizationStoreRoleProvider initializes AzMan only once, key methods such as IsUserInRole and GetRolesForUser, which retrieve information regarding specific users, call a private helper method named GetClientContext to initialize AzMan's client context on every call. If the application employs Windows authentication, GetClientContext initializes the client context from the user's Windows token. If forms authentication is used instead, GetClientContext
initializes the client context from the forms-authentication user name.
Initializing the client context from a Windows token is faster, but the
fact that GetClientContext abstracts the authentication type means that AuthorizationStoreRoleProvider works equally well with Windows authentication and forms authentication. Cache Refresh InitApp uses a Boolean flag named _InitAppDone to avoid redundant execution. Successful execution of InitApp sets _InitAppDone to true. The next time InitApp is called, it returns without doing anything, unless AzMan's IAzAuthorizationStore.UpdateCache method hasn't been called recently—that is, within the time window specified through the provider's CacheRefreshInterval property. In that case, InitApp refreshes the AzMan cache from the underlying data store, by calling UpdateCache on AzMan. CacheRefreshInterval defaults to 60
(minutes), meaning that, by default, it could be up to an hour before
changes made to role definitions and assignments in the data store
propagate to AzMan (and therefore to AuthorizationStoreRoleProvider). If desired, you can change the cache refresh interval, by using the cacheRefreshInterval configuration attribute. The set accessors for AuthorizationStoreRoleProvider's ApplicationName and ScopeName properties set _InitAppDone to false. Therefore, changing these property values at run-time refreshes the cache, regardless of the value of CacheRefreshInterval (or how much time has elapsed since the last call to AzMan's UpdateCache method). Creating Roles Applications call the role manager's Roles.CreateRole method to create new roles. Roles.CreateRole calls the default role provider's CreateRole method. AuthorizationStoreRoleProvider.CreateRole uses the helper method CallMethod to call AzMan's CreateRole
method, allowing the .NET run-time to marshal the managed string
containing the role name into a COM-compatible string, as follows: object[] args = new object[2]; args[0] = roleName; args[1] = null; object role = CallMethod(_ObjAzScope != null ? _ObjAzScope : _ObjAzApplication, "CreateRole", args);
In this example (and many others like it), CallMethod calls the specified method on the application object created by InitApp if the provider lacks a ScopeName, or on the scope object created by InitApp if the provider has a ScopeName. Following a successful call to AzMan's CreateRole method, AuthorizationStoreRoleProvider.CreateRole calls CallMethod again to call Submit on the AzMan role object returned by the previous call, and to commit the new role to the data store. Then, it calls Marshal.FinalReleaseComObject to release the role object. Deleting Roles Applications call the role manager's Roles.DeleteRole method to delete roles. Roles.DeleteRole, in turn, calls the default role provider's DeleteRole method. If the third parameter (throwOnPopulatedRole) passed to DeleteRole is true, AuthorizationStoreRoleProvider.DeleteRole calls AuthorizationStoreRoleProvider.GetUsersInRole to determine whether the role is empty, and throws a ProviderException if it's not. Then it calls AzMan's DeleteRole and Submit methods to delete the role from the data store. Adding Users to Roles and More Other AuthorizationStoreRoleProvider methods do as CreateRole and DeleteRole, using CallMethod and CallProperty to delegate to AzMan methods. For example, AuthorizationStoreRoleProvider.AddUsersToRoles first iterates through the array of role names passed to it, calling AzMan's OpenRole
method to validate each role and convert it into an AzMan role object.
Then, it iterates through all the user names input to it, calling
AzMan's AddMemberName method repeatedly to add the users to the roles, and finishes up by calling Submit on each AzMan role object. Differences Between the Published Source Code and the .NET Framework's AuthorizationStoreRoleProvider The source code for the AuthorizationStoreRoleProvider
is being released unchanged. This means you will not be able to compile
it in its current state, because it contains calls to internal helper
methods. However, you can reference the source code to see exactly how
the provider maps role manager calls to AzMan. Theo
.NET Việt Nam Số lượt đọc:
272
-
Cập nhật lần cuối:
25/04/2008 04:37:06 PM |