Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Asp Net 2.0 Security Membership And Role Management

.pdf
Скачиваний:
51
Добавлен:
17.08.2013
Размер:
12.33 Mб
Скачать

Chapter 14

The code for handling the application name in the custom role provider is exactly the same as was used for writing a custom Membership provider. With this simple change, you can now create roles in different applications and work with user-to-role associations in different applications simply by changing the value of the appname query-string variable. This behavior is also completely transparent to the Role Manager API and the RolePrincipal object. As with Membership though, if you write applications that depend on dynamically changing application name, you need to prevent accidentally associating authorization information for a user in one application with a similarly named user in a different application.

Summar y

The SqlRoleProvider is a complete implementation of the RoleProvider base class with which you can quickly and easily set up user-to-role associations. The simplicity of the provider should not fool you though; the product team tested it regularly with 250,000 user-to-role associations and has stressed the provider with as many as 20 million user-to-role associations. So, even for large sites the provider is quite capable of scaling well with large numbers of users and roles. Note though that the provider does not support one often-asked-for feature: role nesting. In large part, this is because the Role Manager feature itself does not expose the concept of nesting roles within roles.

As with the Membership providers, you can use the SqlRoleProvider both inside of ASP.NET as well as in non-ASP.NET applications. Within ASP.NET the provider needs to run in Low trust or higher. The provider works equally well in partially trusted non-ASP.NET applications, although getting these types of applications to work properly with the provider does require a bit of rather arcane configuration work in the Framework’s CAS system. With that said though, you can definitely get this scenario to work, and it is something that the ASP.NET team intentionally worked to enable in the 2.0 Framework.

Although the SqlRoleProvider is a rather simple provider to implement, you can still use it in a number of interesting ways. You can store authorization information in the database for Windows-authenticated users, which makes the provider ideal for applications where you don’t need the extra time or hassle of getting NT groups setup for application authorization purposes. Because the SqlRoleProvider is unsealed, you can derive from it and add whatever custom authorization logic you want on top of it. In this chapter, you saw how you could use this approach to easily give power users and administrators the ability to restrict the set of roles that they act in while working on a site.

Because the provider’s schema exists in SQL Server, and there are supported SQL views for querying this information, you can create your own custom data layer logic to perform authorization checks using the role data stored in the database. And just as with the Membership providers, you can write a simple derivation of SqlRoleProvider that can handle dynamically changing the application name on a per-request basis for portal-style applications.

572

AuthorizationStoreRole

Provider

AuthorizationStoreRoleProvider maps the functionality of the Role Manager feature onto the Authorization Manager (AzMan) authorization store that was first released as part of

Windows Server 2003. The provider supports most of the RoleProvider functionality as well as handful of AzMan specific settings and behavior. Although AzMan itself has the concept of more granular permission checks that just role checks, AuthorizationStoreRoleProvider only exposes the role based functionality of AzMan.

In this chapter, will you will learn about the following aspects of the

AuthorizationStoreRoleProvider:

How the provider interacts with AzMan

Role Manager functionality supported by the provider

Working with a file-based policy store

Working with an Active Directory AzMan policy store

Using the provider in partial trust

Using the ActiveDirectoryMembershipProvider and

AuthorizationStoreRoleProvider together

Provider Design

The AuthorizationStoreRoleProvider is a wrapper around a subset of the functionality available in Authorization Manager. The provider is supported for use in ASP.NET applications and non-ASP.NET applications. Although the provider depends on Authorization Manager, you can

Chapter 15

use it with Windows authenticated and forms authenticated websites. All of the samples in this chapter use forms authentication and ActiveDirectoryMembershipProvider in conjunction with

AuthorizationStoreRoleProvider.

Authorization Manager is a feature that was first shipped as part of Windows Server 2003, and it supports role-based and “operation-based” security. There is also a runtime component that you can install that enables AzMan on Windows 2000 and Windows XP. AzMan supports role-based security because that has been the most prevalent type of security used by developers. It also introduced the concepts of tasks and operations that can be used to model more granular “things,” which themselves can be authorized. For example, with AzMan, you could create an operation called UpdateAccountData, and then within your application you could ask AzMan if the current user has rights to UpdateAccountData. This is an elegant approach to the common authorization problem of separating authorization administration (adding users to roles, assigning users rights to operations) from the security model of “things” that can be authorized. The fact that you can model very granular operations makes AzMan a powerful authorization engine. Because AuthorizationStoreRoleProvider is an implementation of RoleProvider, the provider only exposes the subset of AzMan that deals specifically with associating users to roles and making checks to see if a user belongs to a role. The provider does not expose the AzMan functionality for making operationand task-based access checks.

AzMan stores authorization information inside of a policy store. This policy store can be deployed in an Active Directory server, in ADAM, or in a plain XML file. If you place the policy store in a directory, you can only use ADAM or a Windows Server 2003 domain controller that has been upgraded to run at the Windows Server 2003 functional level. Note, though, that with the downloadable AzMan runtime you can still have web servers running Windows 2000 or Windows XP that make use of the policy store in a Windows Server 2003 domain controller.

AuthorizationStoreRoleProvider works with AzMan through its COM primary interop assembly (PIA), so from the provider’s standpoint the specific type of store is moot. Some partial trust issues arise when using different stores, but in Full trust the different types of policy store locations are just different connection string values to the provider. With Windows Server 2003 SP1, AzMan did add support for nondomain principals stored in ADAM. This allows developers to use completely standalone ADAM instances and set up AzMan authorization information using ADAM principals. However, this new AzMan functionality isn’t supported by AuthorizationStoreRoleProvider. Even though you can place the policy store in any one of the three locations supported by AzMan, in all cases the users and groups managed in the policy store must come from a domain.

The provider connects to the policy store specified in a connection string and then gets a reference to an AzMan application with a call to IAzAuthorizationStore::OpenApplication. Because AzMan also supports the concept of authorization scopes within an application, AuthorizationStoreRoleProvider has a configuration option that allows you to point the provider at a scope as well. In this case, the provider will internally ensure that any provider methods occur within the desired scope, as opposed to operating at the level of an AzMan application. Because AzMan itself can have multiple applications, as well as multiple scopes within an application, you can use the provider’s ApplicationName and ScopeName properties to point at any application or any scope within an AzMan application. In general though, a single configured instance of AuthorizationStoreRoleProvider works with only a single AzMan application or a single scope in an AzMan application. If you need the provider to work with different AzMan applications or scopes, you should configure a separate provider instance for each AzMan application or scope you need to work with.

574

AuthorizationStoreRoleProvider

The other aspect of the provider’s interaction with AzMan is how the provider gets a reference to a client context that represents a specific user. In AzMan, access checks for operations as well as the information needed for a role check all come from an application context represented as an IAzApplicationContext interface. Because the provider supports the IsUserInRole and GetRolesForUser methods, the provider has a number of different approaches to getting the appropriate client context for a given user:

If an ASP.NET application is configured to use Windows authentication, and the username parameter to the provider exactly matches the username from HttpContext.Current

.User.Name, then the provider initializes an AzMan client context using the token from the current principal’s WindowsIdentity. This initialization approach is the fastest and most efficient way to get the correct client context because it doesn’t incur extra round trips to a directory server. In the AzMan API, this means the provider makes a call to IAzApplication:: InitializeClientContextFromToken. If you pass the value of HttpContext.Current

.User.Identity.Name as the username parameter to IsUserInRole or GetRolesForUser, you will be able to have the provider initialize the client context from the Windows token.

For non-ASP.NET applications, the provider follows the same process, but it looks at Thread

.CurrentPrincipal. For non-ASP.NET applications, ensuring that the thread principal is set up with the correct WindowsPrincipal and WindowsIdentity is the most efficient approach for using the provider.

If your application doesn’t have a WindowsIdentity available (in ASP.NET this would probably mean you are using forms authentication), then the provider falls back and initializes the client context with a call to IAzApplization::InitializeClientContextFromName. This is the AzMan method that allows Authorization Manager to take just a plain string representation of a username (e.g. DOMAIN\USERNAME style or the user@domain.com UPN style) and look up the expansion of that user’s group membership in Active Directory. Although this approach gives you the flexibility to use forms authentication in your web applications, it is slower than the tokenbased approach. Also note that for this approach to work the process identity or the application impersonation identity needs read privileges on the tokenGroupsGlobalAndUniversal attribute of any users that will be authorized in the application. By default, read access on this attribute is granted to members of the built-in domain group Pre-Windows 2000 Compatible Access. If this group has no members in your domain structure (for example, you may have locked down your domain by emptying the membership for this group), then the provider will return an access denied exception from the AzMan layer. You can fix this problem in a number of ways. The easiest approach is to add the appropriate accounts to a different built-in domain group called Windows Authorization Access Group. This group has read access to the tokenGroupsGlobalAndUniversal attribute for all users in the domain. You can also follow

a more granular security approach by granting read access on the attribute at the OU level. This has the benefit of limiting the access granted to a process or application impersonation account to only the users in a specific directory container.

After the provider has the user’s client context in hand, it can use it to get the role information needed by IsUserInRole and GetRolesForUser.

Internally, the provider will call the store’s UpdateCache method to update its cached information after 60 or more minutes have passed. The duration between calls to UpdateCache is configurable, primarily so you can tune the provider to be more or less sensitive to changes in the underlying policy store.

Because AzMan caches the authorization information it loads from the policy store, changes made to previously loaded authorization information are not reflected until the next time the provider asks AzMan to update its cached information.

575

Chapter 15

In terms of unique AzMan functionality that does work with the provider, the following pieces of AzMan functionality will affect the results returned by the provider:

AzMan supports nesting of Windows users and Windows groups in AzMan application groups, as well as nesting of AzMan application groups in other AzMan application groups. When you call the provider’s GetRolesForUser or IsUserInRole methods, the results will reflect these nesting relationships. As noted in Chapter 13, this is a perfect example of being able to support nesting relationships for authorization checks even though the Role Manager feature doesn’t explicitly support this kind of functionality.

AzMan supports groups that have dynamic group membership; these are called LDAP query groups. The provider is oblivious to LDAP query groups. You can’t create LDAP query groups via the provider. However, if you have preconfigured LDAP query groups in an AzMan policy store, the results returned from the provider will reflect a user’s membership (or nonmembership) in the LDAP query groups.

Suppor ted Functionality

AuthorizationStoreRoleProvider implements all of the methods defined on the base RoleProvider class with the exception of the FindUsersInRole method. The provider throws a NotImplementedException from this method, which is a bit of a deviation from the normal practice of throwing a NotSupportedException for such cases. Because the provider is basically a “shim” that maps RoleProvider method calls to their equivalent for AzMan, and AzMan has no concept of searching for users in a role, the FindUsersInRole method was not implemented.

If you have ever worked with the AzMan APIs directly, you are probably already getting an idea of how the provider makes use of AzMan. Internally, each supported RoleProvider method maps directly to a method call on an AzMan interface or object. The complete mapping is shown in the following list:

AddUsersToRoles IAzRole::AddMemberName.

CreateRole — Either IAzApplication::CreateRole or IAzScope::CreateRole.

DeleteRole — Either IAzApplication::DeleteRole or IAzScope::DeleteRole.

FindUsersInRole — Not implemented.

GetAllRoles — Iterates through the roles returned by either the IAzApplization::Roles property or the IAzScope::Roles property.

GetRolesForUser IAzClientContext::GetRoles.

GetUsersInRole IAzRole::MembersName.

IsUserInRole — Retrieves roles from IAzClientContext::GetRoles, and then performs a string comparison between the requested role and the set of roles returned from the AzMan method. The comparison is case-insensitive and uses ordinal comparisons (that is, a caseinsensitive byte-by-byte string comparison using the invariant culture).

RemoveUsersFromRoles IAzRole::DeleteMemberName.

RoleExists — Either IAzApplication::OpenRole or IAzScope::OpenRole.

576

AuthorizationStoreRoleProvider

There aren’t any implemented methods that have special or unexpected functionality. Beyond the internal mappings noted in the preceding list, the AzMan specific aspects of the provider are in the area of a few properties and AzMan specific handling of some configuration attributes.

The provider properties that directly affect how its works with AzMan are described in the following list:

ApplicationName AuthorizationStoreRoleProvider uses this attribute as the name of the AzMan application in the policy store that the provider instance should work with. An

important difference from the SQL providers though is that the trick of overriding this property will not work. Internally, the provider always looks at a private variable that stores the application name; the provider doesn’t call the getter on the public property. The assumption was that normally you would not have hundreds or thousands of AzMan applications in a policy store, so supporting the dynamic switching of application context on a per-request basis didn’t really make sense. Note that this property also has a setter. After changing the application name via a call to the setter the provider will reinitialize its reference to an AzMan application by calling IAzAuthorizationStore::OpenApplication again. This can be useful for limited administrative applications, but because the setter is not thread-safe you need to carefully manage calls to it. Otherwise two simultaneous requests attempting to set ApplicationName will interfere with each other. For this reason, you should configure different provider instances for different AzMan applications needed by your production applications.

ScopeName — This is a custom provider property that allows you to get and set the AzMan scope used by the provider. Normally, you configure the AzMan scope in configuration and then the provider operates within the context of the scope for its entire lifetime. As with the ApplicationName property, ScopeName has a setter that you can use. After calling it, the provider will internally reinitialize its IAzApplization and IAzScope references. However, the setter for ScopeName is also not thread-safe, and so it is really only useful for administrative applications that implement some type of locking to ensure that competing threads don’t tromp on each other’s scope settings. The general guidance is that you should configure separate provider instances for each different AzMan application-scope combination needed by your application.

The AzMan specific configuration properties supported by the provider are:

applicationName — This attribute determines the AzMan application used by the provider. You must explicitly specify a value for this attribute if you want the provider to work. Although the provider will use ASP.NET’s default logic for determining an application name if one is not specified, chances are you don’t have an AzMan application with the same name as your web application’s virtual directory (or executable name in the case of a non-ASP.NET application).

scopeName — This attribute determines the AzMan scope in the AzMan application that will be used by the provider. If you specify the scopeName configuration attribute, be sure that the scope really does exist in the AzMan application pointed at by the applicationName attribute.

cacheRefreshInterval — Controls the interval in minutes between calls to update the cached representation of authorization information. If this attribute is not specified, the provider will call UpdateCache on the policy store every 60 minutes. You can lower the value on this setting if you have frequent changes occurring in your policy store, or you can increase it if your policy store doesn’t change much. Note though that this setting affects only cached information derived from the AzMan policy store. For example, if you change the Windows groups that a user belongs to, adjusting this cache interval will not help because the AzMan cache has nothing to do with Windows group memberships that are cached in a user’s security token.

577

Chapter 15

Using a File-Based Policy Store

You can configure AzMan’s authorization rules using an XML file as opposed to a directory — in this case, the XML file is the policy store. AzMan supports a file-specific connection string format for connecting to an XML file. AuthorizationStoreRoleProvider is configured with this connection string in the same way that you would configure a SQL-based provider with an ADO.NET-compliant connection string. You add the connectionStringName attribute to your provider definition and it references a connection string in the <connectionStrings /> section. For example, the following connection string uses a combination of the AzMan connection string syntax and a special syntax that is unique specifically to AuthorizationStoreRoleProvider:

<add name=”FileBasedPolicyStore” connectionString=”msxml://~/App_Data/test.xml”/>

The bolded portion of the connection string uses the ASP.NET tilde shorthand. When the provider sees that the connection string starts with msxml it knows that it will be working with a file-based policy store. As a result the provider makes an extra check for the tilde syntax. If it finds it, the provider gets the physical file path to the root of the web application and prepends it to the remainder of the connection string. In the preceding sample syntax, this means you could also use a connection string such as:

<add name=”FileBasedPolicyStore” connectionString=

“msxml://D:\Inetpub\wwwroot\Chapter15\UsingAzMan\App_Data\test.xml”/>

For web applications, it makes sense to use the ~/App_Data shorthand because you can just deploy the web.config file onto a web server without having to fix up the file path for the AzMan policy store. If you use the provider in a non-ASP.NET application, you can actually use the same tilde syntax. In this case the provider substitutes the file path to the current executable in place of the tilde character.

Using the provider with a file based policy store is trivial after the authorization store has been set up and configured. In Figure 15-1 I have added the demouser98@corsair.com account to a role called Normal Users. There is also another role called Adminstrators defined in the application called

UsingAzMan.

At this point, using the policy store is just an exercise in configuring Role Manager properly, and then calling the APIs. The abbreviated configuration for a test application is:

<connectionStrings>

<!--special file based syntax supported only by the provider--> <add name=”FileBasedPolicyStore”

connectionString=”msxml://~/App_Data/test.xml”/> </connectionStrings>

...

<roleManager enabled=”true” defaultProvider=”fileProvider”> <providers>

<clear />

<add name=”fileProvider” type=”System.Web.Security.AuthorizationStoreRoleProvider, ...” connectionStringName=”FileBasedPolicyStore” applicationName=”UsingAzMan”/>

</providers>

</roleManager>

578

AuthorizationStoreRoleProvider

Figure 15-1

The provider definition points at a connection string using the tilde shorthand. The applicationName attribute on the provider definition corresponds to the AzMan application UsingAzMan that you can see in the policy store from Figure 15-1.

With the configuration steps completed, you can create roles and associate users to roles. If you want you can use the Web Administration Tool (WAT) to accomplish this. Because the WAT is oblivious to the type of provider being used, it will allow you to carry out role management against AzMan via the provider. Because I used the ActiveDirectoryMembershipProvider for my sample application, the WAT was able to find users and assign them to roles managed by the AuthorizationStoreRoleProvider. After you have setup some roles and user-to-role assignments, you can dump out the roles that the user belongs to.

string[] roles = ((RolePrincipal)User).GetRoles(); foreach (string r in roles)

Response.Write(User.Identity.Name + “ is in role <b> “ + r + “</b><br/>”);

This code snippet shows that the user account belongs to the Normal Users role:

demouser98@corsair.com is in role Normal Users

579

Chapter 15

If you go back into the AzMan MMC and switch the account over to the Administrators role, you can see the change in role assignment take effect. First though you will need to cycle the web application (touch web.config or iisreset). This is because after browsing to the test page the first time, AzMan will have cached the results of the policy lookup. Changing a user’s role membership in the MMC won’t be reflected in the AzMan runtime until the next cache refresh interval (remember that the provider uses a 60 minute cache refresh interval by default). After you have cycled the web application, thus dumping the cached AzMan authorization information, refreshing the page in the browser will show the new role membership.

Note that from AzMan’s point of view, the file is just an XML file, which has security implications for your web application. For web applications you should always place the XML file (or files if you are configuring multiple provider instances) inside of the App_Data directory. This prevents malicious users from downloading the policy store. If you were to place the XML file somewhere else in your directory structure, browser users that guessed the name of it would be able to download your entire authorization policy!

Of course, this raises the question of whether you should use file-based policy stores in production applications. Personally, I would lean away from doing so and limit use of the file-based policy store to development environments. Even though the policy store will be safe when it resides in App_Data, it still seems risky to have your authorization policy sitting on your web server’s hard drive, available for anyone with local server access to browse. Some folks though like to use file-based policy stores in production because if the policy store is small (“small” is relative but 1MB or smaller is a reasonable “guesstimate”), using an XML-based store is much faster than using the directory based store. Another argument against not using a file-based policy store is that in a web farm you now have the hassle of

having to push updates to your authorization policy across multiple machines. Determining whether all of your servers have the same authorization rules can be a bit difficult. If you store the policy in a directory, you know that every web server pointed at the directory server is seeing the same consistent set of authorization information.

Because the policy store exists in a file, you can secure access to the store with NTFS file ACLs. Like other ASP.NET providers, AuthorizationStoreRoleProvider internally runs with either the process credentials or application impersonation credentials assuming you have application impersonation enabled. If these credentials only have read access to the policy store, only the read-oriented methods on the provider will succeed. If the process or application impersonation credentials have write access to the file as well, then write-oriented methods (for example, CreateRole) will also work.

The default App_Data credentials set by Visual Studio grant both read and write access to the process account. As a result, for file-based policy stores, your web application will be able to modify the information in the store by default. To restrict policy stores to read-only on your web servers you can simply revoke Write permission on the XML file from the process account or application impersonation account.

Using a Director y-Based Policy Store

From a programming and configuration standpoint, using a directory-based policy store is no different than using a file-based policy store aside from the connection string. Instead of configuring the connection string with an msxml moniker, you use an msldap moniker with a valid LDAP path. Setting up an AzMan policy store basically involves choosing a location for the store in your directory. Instead of storing the policy store in a file, the policy store is located in a container somewhere in your directory

structure. I created a policy store in the directory structure that you saw used earlier in Chapter 12 when you learned about working with ActiveDirectoryMembershipProvider. Figure 15-2 shows a policy store aptly named “Chapter 15” that contains an application called UsingAzMan.

580

AuthorizationStoreRoleProvider

Figure 15-2

If you look at the containers underneath corsair.com, you will see there is a container titled Chapter15 that is of type msDS-AzAdminManager. This container is the root of the AzMan policy store shown in Figure 15-2. Note that you won’t see this container unless you enabled the advanced features view in the Active Directory MMC. Normally though, you work with the AzMan policy store via the AzMan MMC. Looking at the underlying container location is interesting in order to get an idea of how the abstract concept of a policy store maps to a physical container within a directory.

With the policy store and AzMan application created, you can connect to it with the following connection string:

<add name=”DirectoryBasedPolicyStore” connectionString=

“msldap://corsdc2.corsair.com/CN=Chapter15,DC=corsair,DC=com”/>

Unlike ActiveDirectoryMembershipProvider, where you could also use just a domain name, AzMan requires a server name and optional port name if you choose to supply this information. If you want though, you can skip the servername and port name, in which case AzMan will use the default domain controller selected by the machine. The following connection string shows what this looks like:

581