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

Asp Net 2.0 Security Membership And Role Management

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

Chapter 15

<add name=”DirectoryBasedPolicyStore” connectionString=”msldap://CN=Chapter15,DC=corsair,DC=com”/>

At this point, you might think you could take the sample code from the file-based policy example shown earlier and just use one of these two connection strings. If you do this, your code will connect to the policy store and then will promptly fail with an exception stating “Insufficient access rights to perform the operation.” This is because the identity of your web application doesn’t have any privileges to read or write information in the directory’s policy store. Unlike the file-based policy store where NTFS ACLs control rights to the store, in a directory store you must explicitly setup the AzMan “roles” that grant access to applications and scopes.

I put “roles” in quotes because it can quickly become confusing dealing with AzMan “roles” used for connection access versus the real role information in the policy store. AzMan defines an Administrator role and a Reader role that control the kinds of operations a security account can perform in a policy store or application. As you would expect, a member of the Administrator role can do things like create applications, scopes, and roles. A member of the Reader role can only query information in the policy store — it cannot modify it.

Because I need to populate the store with some roles and setup a user-to-role mapping, I initially added the web server’s machine account (which corresponds to NETWORK SERVICE) to the Administrator role for the AzMan application called “UsingAzMan”. You can see what this looks like in Figure 15-3.

Figure 15-3

582

AuthorizationStoreRoleProvider

As you can see from the screenshot, only Enterprise Admins are members of this role by default. For a development environment where you are just loading test data, adding a server account to the Administrator role is acceptable. However, in a production environment, you clearly should not have your process accounts or application impersonation accounts in this role. At most you might have a machine off to the side running an administrative application, where the process or application impersonation credentials for that application are in the Administrator role.

Because the NETWORK SERVICE account was added to a management role associated with an AzMan application, you also need to add the machine account to the Delegated User role at the store level. You can see this in Figure 15-4.

This extra step is necessary if you plan to delegate control over different applications, or different scopes within a single policy store. If you plan to store only a single web application’s authorization information in a single policy store, then you can just grant rights at the store level (this would be a model of one business application mapping to one AzMan policy store). On the other hand, if you plan to store many different sets of authorization information within a single AzMan policy store, chances are that you don’t want different web applications accidentally making use of each others authorization rules.

Figure 15-4

583

Chapter 15

In this case, you may allocate an AzMan application for each of your business applications, or you may allocate an AzMan scope for each business application. For these scenarios you need more granular access control down to the level of an AzMan application or an AzMan scope. As a result, you start out adding the appropriate accounts to the store level Delegated User group, and then add the appropriate accounts (that is, delegate control) to the Administrator or Reader role on a specific application or scope.

With this extra set of security configuration completed, you can now run the sample code from the filebased sample. The configuration looks almost exactly the same:

<connectionStrings>

<add name=”DirectoryConnection” connectionString=

“LDAP://corsdc2.corsair.com/OU=UserPopulation_A,DC=corsair,DC=com”/>

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

</connectionStrings>

...

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

<clear />

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

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

</providers>

</roleManager>

A second provider instance using a directory-based policy store was added to the <roleManager /> definition and was made the default provider for the feature. At this point, you can start creating roles and assigning users to roles. If you are running as an interactive user with privileges in the AzMan policy store, you can use the WAT to accomplish this. Alternatively, now that the process account is part of the application’s Administrator role you can use the standard Role Manager APIs in .aspx pages to create roles and populate the roles with users.

Because most developers will probably work with prepopulated policy stores in their production environments, you can change the rights that have been delegated to the process account or application impersonation account. Although the account still needs to be in the Delegated User role at the store level (assuming that you want to work with many applications in a single policy store), you can instead add the account to the Reader role for the application. This will allow your application to read authorization information, but it won’t be able to modify it in any way.

As noted earlier, the provider also supports working within the context of an AzMan scope. You can change the configuration for the provider to include a scope definition like shown below:

<add name=”directoryProvider” type=”System.Web.Security.AuthorizationStoreRoleProvider, ...” connectionStringName=”DirectoryBasedPolicyStore”

584

AuthorizationStoreRoleProvider

applicationName=”UsingAzMan” scopeName=”Scope_A”/>

Now, if you create new roles and assign users to roles, all of the operations will be occurring within the Scope_A scope nested within the UsingAzMan application. Figure 15-5 shows what this looks like.

The code to create the new roles and populate the roles consists of standard Role Manger API calls:

if (Roles.RoleExists(“Administrators in Scope A”)) Roles.DeleteRole(“Administrators in Scope A”, false);

if (Roles.RoleExists(“Normal Users in Scope A”)) Roles.DeleteRole(“Normal Users in Scope A”, false);

Roles.CreateRole(“Administrators in Scope A”);

Roles.CreateRole(“Normal Users in Scope A”);

if (!Roles.IsUserInRole(“Administrators in Scope A”)) Roles.AddUserToRole(User.Identity.Name, “Administrators in Scope A”);

Figure 15-5

585

Chapter 15

As you can see, from a programming perspective nothing changes. You continue to write Role Manager code as you normally would, and the provider automatically takes care of working against the correct application scope.

Another unique aspect of using the AzMan policy store is the ability to nest group memberships. There are a variety of approaches to nesting:

Add Windows users and Windows groups directly to a role you create in AzMan.

Add Windows users and Windows groups to an AzMan application group. Then add the AzMan application group to a role you create in AzMan.

Add Windows users and Windows groups to an AzMan application group. Then add the AzMan application group to a different AzMan application group. Add this second group to a role you create in AzMan.

So, you have quite a few different options that allow you to accomplish group nesting. Although AuthorizationStoreRoleProvider can add Windows users only directly to an AzMan role, the provider will properly handle the necessary group expansion computations when IsUserInRole or GetRolesForUser is called (or more precisely AzMan does this for you).

To see how this works you can setup some test AzMan application groups. Set up an application group hierarchy like the following:

Application Group That Contains A

|

|

---> Application Group A

|

|

---> demouser98@corsair.com

You now have an example of a nesting relationship. The demouser98@corsair.com user account in Active Directory indirectly belongs to the top-level AzMan application group called Application Group That Contains A. You can add this application group to the Normal Users role that was created earlier as shown in Figure 15-6.

Now if you dump all the roles that “emouser98@corsair.com belongs to with the following code:

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

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

You will see the following output:

demouser98@corsair.com is in role Administrators demouser98@corsair.com is in role Normal Users

586

AuthorizationStoreRoleProvider

Figure 15-6

Even though the user belongs to Normal Users by way of two intervening application groups, the provider is properly returning the full expansion of the user’s role membership. If the underlying call to the provider’s GetRolesForUser method did not properly expand all nested group relationships when computing a user’s AzMan role memberships, the utility of the provider, and for that matter AzMan itself, would be rather hobbled. Keep this behavior in mind if you plan on using AuthorizationStoreRoleProvider. Even though you won’t get the benefit of the AzMan access checks with this provider, the ability to use any type of group nesting in AzMan and still have role checks work properly gives you a powerful piece of role management that SqlRoleProvider lacks.

One other unique aspect of AzMan that you can leverage with the provider is LDAP query groups. The AzMan application groups you just saw are called basic groups in AzMan terminology. The companion group type in AzMan is an LDAP query group. As the name suggests, instead of statically defining the users and groups that belong to the AzMan application group, membership is determined on the fly based on an LDAP query. Depending upon what kind of user information you store in your directory, you can create some very rich user-to-LDAP query group assignments (for example, users that belong to the West coast region, users that have a specific area code, and so on).

587

Chapter 15

Even though the concept of a MembershipUser in ASP.NET is very limited, this doesn’t constrain the kinds of LDAP queries you can use in AzMan. This means that if you have some way of populating attributes for your user objects other than the Membership feature, you can create LDAP queries that make use of this information. For example, if you set the zip code (that is, the postalCode attribute) on a user object, you can then construct an LDAP query group that predicates its membership based on this value. A simple example of such a query definition is shown in Figure 15-7.

You can then add the LDAP query group to one of the AzMan roles that created earlier. Figure 15-8 shows adding the query group to the role called. This is a new role.

I edited the user object for the demouser98@corsair.com user by setting its zip code to 98005. Now if you rerun the sample code that prints out a user’s roles, you can see that the provider returns the third AzMan role as well.

demouser98@corsair.com is in role Administrators demouser98@corsair.com is in role Normal Users demouser98@corsair.com is in role This is a new role

Figure 15-7

588

AuthorizationStoreRoleProvider

Figure 15-8

Even though this kind of dynamic group functionality is not defined anywhere in the Role Manager feature, you can still take advantage of it via AuthorizationStoreRoleProvider. As long as you have set up user attributes and LDAP query groups through some other mechanism, you can take full advantage of the dynamic membership of LDAP query groups with the provider. With some planning around user attributes and LDAP queries you can structure your AzMan authorization rules to automatically adjust to the changing information stored for your users.

Working in Par tial Trust

Because the provider works with both file-based AzMan policy stores and directory-based AzMan policy stores, there are two different approaches to getting the provider working in a partially trusted application. Regardless of the policy store location, the provider always requires AspNetHostingPermission with at least Low trust (see Chapter 14 on SqlRoleProvider to learn how you can grant this permission in a nonASP.NET application) during the initialization process.

589

Chapter 15

The provider always checks for AspNetHostingPermission with a setting of Medium for any writeoriented methods. Because Low trust is conceptually a read-only trust bucket, while Medium trust is the conceptual read-write trust bucket, AuthorizationStoreRoleProvider only allows the following methods to work when running in a web application at Medium trust or above:

CreateRole

DeleteRole

AddUsersToRoles

RemoveUsersFromRoles

You will see this behavior for ASP.NET applications. If you plan to use the provider outside of ASP.NET in a partial trust application, you effectively need to run at full trust as is discussed a bit later in this section.

If the policy store is located in an XML file, and you are using the provider inside of an ASP.NET application, then the provider will also partially rely on the application’s file I/O code access security (CAS) permissions for read-oriented methods. The idea here is that if you are using a file-based policy store, then the file I/O CAS permissions of the application are a good indicator of whether a partial trust web application has rights to use the provider. When the provider is initializing itself, it will check to see if the web application has read access to the XML file. This effectively means that in High trust you can point the provider at a policy file that is located anywhere on the file system. In Medium and Low trust though, due to the FileIOPermission(s) granted at these trust levels, the provider will only work with a policy file located somewhere within the application’s directory structure. This kind of restriction makes sense because you probably do not want a Medium or Low trust application to read policy files located in other applications’ directory structures. Assuming that your application passes these trust level checks, the provider internally asserts unrestricted security permissions so that it can call into the AzMan PIA without triggering any security exceptions.

To demonstrate how all of this works you can take a sample application like the one shown earlier for the file-based policy store, and change the trust level setting. For example, if you drop the trust level down to Low, and then attempt to create or delete roles you will get an error stating “This API is not supported at this trust level.” If you bump the trust level up to Medium though, role creation and deletion will work again. However, if you reset the trust level to Low, you will still be able to use read-only methods like GetRolesForUser. Also, in both Medium and Low trust if you change the connection string to point a location outside of the web application’s directory structure you will get an exception like the following:

[HttpException (0x80004005): Access to path ‘test.xml’ was denied. The location does not exist or is not accessible because of security settings.] System.Web.HttpRuntime.CheckFilePermission(String path, Boolean writePermissions) System.Web.Security.AuthorizationStoreRoleProvider.InitApp() System.Web.Security.AuthorizationStoreRoleProvider.GetClientContext(String userName) System.Web.Security.AuthorizationStoreRoleProvider.GetRolesForUserCore(String username) System.Web.Security.AuthorizationStoreRoleProvider.GetRolesForUser(String username)

...

From the stack trace you can see that the provider is explicitly checking for FileIOPermission by way of an internal HttpRuntime helper method and that this check causes the failure.

590

AuthorizationStoreRoleProvider

If you use the provider in a partial trust web application and your policy store is located in a directory store, your code will simply not work regardless of the configuration steps you take. For example, if you run an application in High trust and attempt to use the provider, you will instead get error information like the following:

[SecurityException: Request for the permission of type ‘System.Security.Permissions.SecurityPermission...’ failed.]

...

System.Activator.CreateInstance(Type type, Boolean nonPublic) System.Activator.CreateInstance(Type type) System.Web.Security.AuthorizationStoreRoleProvider.InitApp() System.Web.Security.AuthorizationStoreRoleProvider.GetClientContext(String userName) System.Web.Security.AuthorizationStoreRoleProvider.GetRolesForUserCore(String username) System.Web.Security.AuthorizationStoreRoleProvider.GetRolesForUser(String username) System.Web.Security.RolePrincipal.GetRoles() +248

...

In this case, when the provider attempts to open the policy store via the AzMan PIA, the call fails. Like many CAS-related errors the error information is less than enlightening, and you can’t tell what the problem is. Futhermore, the stack trace shows the provider calling Activator.CreateInstance, which probably seems a bit weird. Internally, the provider actually does not have any compile time dependency on the AzMan PIA. Instead the provider dynamically loads AzMan types through reflection and then invokes methods on the resulting runtime callable wrappers through reflection as well. I intentionally chose High trust to demonstrate the error condition because High trust applications do have full reflection permissions. So, clearly it is not a lack of reflection permissions that is causing the security error.

The reason for the error is that the provider and the rest of the call stack require unmanaged code permissions to call into the COM PIA. There is no reasonable surrogate permission that can be used

by the provider in return for asserting unmanaged code permission (as is done in the case of a file-based policy store) when connecting to a directory based policy store. Neither AspNetHostingPermission nor FileIOPermission make sense to use as surrogate permissions. Theoretically, the development team could have used DirectoryServicesPermission that you saw in Chapter 12 on ActiveDirectoryMembershipProvider, but doing so would be a bit awkward. Granting

DirectoryServicesPermission just to get AuhtorizationStoreRoleProvider working would also mean that any code in your web application could use the System.DirectoryServices class and start connecting to arbitrary directory stores. That level of access was considered excessive just for enabling a single provider.

Instead, if you want to use the provider in a partial trust web application with a directory-based policy store, you will need to wrap the calls to the provider’s methods inside of a trusted GAC’d assembly. The wrapper assembly will need to assert a SecurityPermission for unmanaged code permissions prior to calling into the provider because internally the provider uses the AzMan PIA to talk to AzMan through COM interop. Because COM classes are considered unmanaged classes, a wrapper assembly must assert unmanaged code permissions.

So, far I have discussed how to use the provider in partial trust web applications. For partial trust nonASP.NET applications, you always need unmanaged code permissions. This holds true even for file based policy stores. This means you need some kind of trusted code on the stack that calls into the provider. As a result using a GAC’d wrapper assembly that asserts unmanaged code permissions is the correct approach for using the provider in partially trusted non-ASP.NET applications.

591