Role Providers

 

Microsoft ASP.NET 2.0 Providers: Introduction
Membership Providers
Role Providers
Site Map Providers
Session State Providers
Profile Providers
Web Event Providers
Web Parts Personalization Providers

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:

  1. Calls aspnet_Applications_CreateApplication to retrieve an application ID (or create a new one).
  2. Verifies that the specified role doesn't already exist—that is, that it's not already defined in the aspnet_Roles table.
  3. 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:

  1. Verifies that the role to be deleted exists.
  2. 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.
  3. Deletes all records containing the specified role ID from the aspnet_UsersInRole table.
  4. 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:

  1. 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.
  2. 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.
  3. Calls AzMan's IAzAuthorizationStore.Initialize method, passing in the connection string retrieved from the configuration.
  4. 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).
  5. 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.

Return to part 2, Membership Providers.

Go on to part 4, Site Map Providers.

© Microsoft Corporation. All rights reserved.