Pro ASP.NET 2.0 In CSharp 2005 (2005) [eng]
.pdf
788 C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S
In both cases, the tool adds a little configuration entry to the application’s web.config file. You can do this manually, just as you can enable the Roles Service.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.web>
<roleManager enabled="true" /> <authentication mode="Forms" />
</system.web>
</configuration>
With this configuration in place, ASP.NET automatically creates a file-based database, ASPNETDB.MDF, in the application’s App_Data directory, as already described in Chapter 21. If you want to use a custom store, you have to complete the following steps:
1.Create the data store either by using aspnet_regsql.exe or by executing the TSQL command scripts included in the .NET Framework directory. Both were introduced in Chapter 21.
2.Configure the Roles provider to use the previously created custom store.
You can configure the Roles provider through the <roleManager> tag. You can either use a different database or use a completely different store if you want. In addition, you can configure certain properties through the <roleManager> tag that can’t be configured in the WAT.
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <connectionStrings>
<add name="MySqlStore" connectionString="data source=(local);
Integrated Security=SSPI;initial catalog=MySqlDB"/> </connectionStrings>
<system.web>
<roleManager enabled="true" defaultProvider="MySqlProvider" cacheRolesInCookie="true" cookieName=".MyRolesCookie" cookieTimeout="30" cookieSlidingExpiration="true" cookieProtection="All">
<providers>
<add name="MySqlProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="MySqlStore" applicationName="RolesDemo"/>
</providers>
</roleManager> <authentication mode="Forms"/> <compilation debug="true"/>
</system.web>
</configuration>
As soon as you have added this configuration entry to your web.config file, you can select the provider through the WAT. Just switch to the Provider tab, and then click the link Select a Different Provider for Each Feature. Figure 23-3 shows the provider selection in the WAT.
790 C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S
Table 23-1. Continued
Option |
Description |
cookieRequireSSL |
Specifies whether the cookie will be returned by ASP.NET only if SSL is |
|
enabled (true) or in any other case (false). If this attribute is set to true |
|
and SSL is not activated, the runtime simply doesn’t return the cookie, |
|
and therefore role checks always happen against the underlying Roles |
|
provider. |
cookieTimeout |
Gets or sets a timeout for the roles cookie in minutes with a default of |
|
30 minutes. |
createPersistentCookie |
If set to true, the cookie will be stored persistently on the client |
|
machine. Otherwise, the cookie is just a session cookie that will be |
|
deleted when the user is closing the browser. |
domain |
Specifies the valid domain for the role cookie. |
maxCachedResults |
Specifies the maximum number of role names persisted in the cookie. |
|
|
In the previous example, you configured the SqlRoleProvider. The provider includes a couple of additional settings you can configure through web.config, as shown in Table 23-2.
Table 23-2. Additional Properties of the SqlRoleProvider
Property |
Description |
name |
Name of the provider. This name can be used in the defaultProvider |
|
attribute described in Table 23-1 for specifying the provider by the |
|
application. |
applicationName |
Name of the application for which the roles are managed. |
description |
Short, friendly description of the provider. |
connectionStringName |
Name of the connection string specified in the web.config file’s |
|
<connectionStrings> section that will be used for connecting to the |
|
back-end roles store. |
|
|
In addition to the SqlRoleProvider, ASP.NET ships with a provider that can be used on Windows Server 2003 with Authorization Manager. You can also create and use your own custom providers, as you will learn in Chapter 26. Table 23-3 shows the classes included in the Roles Service framework.
Table 23-3. The Fundamental Roles Service Classes
Class |
Description |
RoleManagerModule |
This module ensures that roles will be assigned to the currently |
|
logged-on user for every request. It attaches to the Application_ |
|
AuthenticateRequest event and creates an instance of RolePrincipal |
|
containing the roles the user is assigned to automatically if the Roles |
|
Service is enabled in web.config. |
RoleProvider |
Base class for every Roles provider that defines the interface you must |
|
implement for a custom RoleProvider. Every custom provider must be |
|
inherited from this class. |
RoleProviderCollection |
A collection of Roles providers. This collection allows you to iterate |
|
through the configured Roles providers on your system and for your |
|
application. |
SqlRoleProvider |
Implementation of a Roles provider for SQL Server–based databases. |
C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S |
791 |
Class |
Description |
WindowsTokenRoleProvider |
Gets role information for an authenticated Windows user |
|
based on Windows group associations. |
AuthorizationStoreRoleProvider |
Implementation of a Roles provider for storing roles in a |
|
Authorization Manager–based store. Authorization Manager |
|
ships with Windows Server 2003 and allows you to declara- |
|
tively define application roles and permissions for this role. |
|
Your application can use Authorization Manager for |
|
programmatically authorizing users. |
Roles |
You use the Roles class as your primary interface to the roles |
|
store. This class includes methods for programmatically |
|
managing roles. |
RolePrincipal |
This is a IPrincipal implementation that connects the |
|
configured roles with the authenticated user. It is created |
|
automatically by the RoleManagerModule if the Roles |
|
Service is enabled. |
|
|
As soon as you have configured the Roles Service, you can create users and roles and then assign users to these roles using either the WAT or the Roles class in your code. On the Security tab, just click the Create or Manage Roles link. Then you can create roles and add users to roles, as shown in Figure 23-4.
Figure 23-4. Adding users to roles
After you have configured users and roles, you need to configure the authorization rules for your application. You have already learned all the necessary details. Just configure the appropriate <authorization> sections in the different directories of your application. Fortunately, you even don’t have to do this manually. When selecting the Security tab, you just need to click one of the links in the Access Rules section, as shown in Figure 23-5.
792 C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S
Figure 23-5. Configuring access rules with the WAT
When the Roles Service is enabled, the RoleManagerModule automatically creates a RolePrincipal instance containing both the authenticated user’s identity and the roles of the user. The RolePrincipal is just a custom implementation of IPrincipal, which is the base interface for all principal classes. It therefore supports the default functionality, such as access to the authenticated identity and a method for verifying a role membership condition through the IsInRole() method. Furthermore, it employs a couple of additional properties for accessing more detailed information about the principal. You can use the properties in the following code for extracting information from the instance as well as for performing authorization checks by calling the IsInRole() method:
protected void Page_Load(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
RolePrincipal rp = (RolePrincipal)User;
StringBuilder RoleInfo = new StringBuilder();
RoleInfo.AppendFormat("<h2>Welcome {0}</h2>", rp.Identity.Name);
RoleInfo.AppendFormat("<b>Provider:</b> {0}<BR>", rp.ProviderName);
RoleInfo.AppendFormat("<b>Version:</b> {0}<BR>", rp.Version);
RoleInfo.AppendFormat("<b>Expires at:</b> {0}<BR>", rp.ExpireDate);
RoleInfo.Append("<b>Roles:</b> ");
string[] roles = rp.GetRoles();
for (int i = 0; i < roles.Length; i++)
{
if (i > 0) RoleInfo.Append(", ");
C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S |
793 |
RoleInfo.Append(roles[i]);
}
LabelRoleInformation.Text = RoleInfo.ToString();
}
}
Using the LoginView Control with Roles
In the previous chapter, you learned details about the security controls that ship with ASP.NET. One of these controls is the LoginView control. You used this control in Chapter 21 for displaying different controls for anonymous and logged-in users. The control uses templates for implementing this functionality. In Chapter 21 you used the <LoggedInTemplate> and <AnonymousTemplate> templates.
The control supports one additional template that enables you to create different views based on the roles to which a user belongs. For this purpose you need to add a RoleGroups template with <asp:RoleGroup> controls. Within every <asp:RoleGroup> control, you specify a comma-separated list of roles in the Roles attribute for which its <ContentTemplate> will be displayed, as follows:
<asp:LoginView runat="server" ID="MainView"> <LoggedInTemplate>
<h2>This is the logged in template</h2> </LoggedInTemplate>
<RoleGroups>
<asp:RoleGroup Roles="Admin"> <ContentTemplate>
<h2>Only Admins will see this</h2> </ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Contributor"> <ContentTemplate>
<h2>This is for contributors!</h2> </ContentTemplate>
</asp:RoleGroup>
<asp:RoleGroup Roles="Reader, Designer"> <ContentTemplate>
<h2>This is for web designers and readers</h2> </ContentTemplate>
</asp:RoleGroup>
</RoleGroups>
</asp:LoginView>
The LoginView control in the previous code displays different content for logged-in users and for users assigned to specific roles. For example, for users in the Admin role the control displays the text “Only Admins will see this,” while for users in the Contributor role it displays the text “This is for contributors.” Also, for users who are associated with the Reader or Designer role, it displays different content.
It’s important to understand that just one of these templates will be displayed. The control simply displays the first template that fits the logged-in user. For example, if you have a user associated with the Contributor, Reader, and Designer roles, the first matching template is
the <asp:RoleGroup> for contributors. The other role group will simply not be displayed. The LoggedInTemplate, for example, will be displayed only for authenticated users with no matching <asp:RoleGroup> element. As soon as a matching role group is found, the contents of the LoggedInTemplate will not be displayed.
794 C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S
Accessing Roles Programmatically
As is the case for the Membership API introduced in Chapter 21, the Roles Service includes an API that allows you to perform all tasks from code. You can programmatically add new roles, read role information, and delete roles from your application. Furthermore, you can associate users with roles as well as get users associated with a specific role. You can do all this by calling methods of the Roles class.
Most of the properties included in the Roles class just map to the settings for the <roleManager> tag described in Table 23-1. Therefore, Table 23-4 includes the additional properties and the Roles class’s methods that you can use for managing and accessing the Roles Service programmatically.
Table 23-4. Members of the Roles Class
Member |
Description |
Provider |
Returns the provider currently used by your application. |
Providers |
Returns a collection of all the available providers on the system and |
|
for your application. It therefore returns the providers configured in |
|
machine.config and in web.config of your application. |
AddUserToRole |
Accepts a user name and a role name as a string parameter and adds |
|
the specified user to the specified role. |
AddUserToRoles |
Accepts a user name as a string parameter and role names as an |
|
array of strings and adds the specified user to all the roles specified |
|
in the role names parameter. |
AddUsersToRole |
Accepts a string array with user names and a string parameter that |
|
specifies a role name and adds all the specified users to the role |
|
specified in the second parameter. |
AddUsersToRoles |
Accepts a string array with user names and a second one with role |
|
names and adds all the users in the user names parameter to all the |
|
roles in the role names parameter. |
CreateRole |
Creates a new role. |
DeleteRole |
Deletes an existing role. |
FindUsersInRole |
Accepts a string array with a list of role names and a string |
|
parameter with a list of user names. It returns every user specified |
|
in the user names array that is associated with one of the roles |
|
specified in the array of role names. |
GetAllRoles |
Returns a string array containing all the role names of the roles |
|
available in the role store of the configured provider. |
GetRolesForUser |
Returns a string array containing all the roles the specified user is |
|
associated with. |
GetUsersInRole |
Returns a list of users who are associated with the role passed in as a |
|
parameter. |
IsUserInRole |
Returns true if the specified user is a member of the specified role. |
RemoveUserFromRole |
Removes a single user from the specified role. |
RemoveUserFromRoles |
Removes the specified user from all roles specified. |
RemoveUsersFromRole |
Removes all the specified users from a single role. |
RemoveUsersFromRoles |
Removes all the specified users from all the specified roles. |
RoleExists |
Returns true if a role exists and otherwise false. |
|
|
C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S |
795 |
A good use for accessing roles programmatically is to associate users to roles automatically when they register themselves. Of course, this is useful only for specific roles. Imagine that your application supports a role called Everyone, and every single user should be a member of this role. If you register users on your own, you can enter this relationship manually. But if your application supports self-registration for Internet users, you can’t do this. Therefore, you somehow have to make sure users will be associated with the Everyone role automatically.
With your first attempt, you might want to catch the CreatedUser event of the CreateUserWizard control, but that’s not sufficient. Remember the existence of the ASP.NET WAT, where you can create users. In this case, catching the CreatedUser event of the control placed in your application won’t help. Therefore, you have to find a different solution. You definitely need an applicationwide event for this purpose, although this will not be raised by the configuration application because it is a different application. One possibility is to catch the Application_AuthenticateRequest event; within the event you verify whether the user is a member of the Everyone class. If not, you can add the user automatically. This shifts the task of adding a user automatically to the role to the point of authentication, which definitely affects every user. To do so, you just have to add a global application class to your project and add the following code.
■Caution Of course, you should do something like this only for the lowest privileged roles such as Everyone. It’s never a good idea to perform such an action for any other type of role.
void Application_AuthenticateRequest(Object sender, EventArgs e)
{
if (User != null)
{
if (User.Identity.IsAuthenticated && Roles.Enabled)
{
string EveryoneRoleName = ConfigurationManager.AppSettings["EveryoneRoleName"];
if (!Roles.IsUserInRole(EveryoneRoleName) && Roles.RoleExists(EveryoneRoleName))
{
Roles.AddUserToRole(User.Identity.Name, EveryoneRoleName);
}
}
}
}
The previous code reads the name of the Everyone role from the configuration file so that it is not hard-coded into the application. It then uses the Roles class to check whether the user is already associated with the role, and if not, it checks whether the role exists. If the user is not associated with the role, and the user exists in the system, it uses the Roles.AddUsersToRole method for programmatically adding the user to the Everyone role.
■Caution You might want to use the User.IsInRole() in the previous code; however, this is not valid. When the application-wide Application_AuthenticateRequest is called, the RoleManagerModule itself has not been called yet. Therefore, the RolePrincipal with the association of the user and its roles has not been created yet, so a call such as User.IsInRole("Everyone") would return false. Later in your page code—for example, in a Page_Load routine— the RolePrincipal is already initialized, and the call to User.IsInRole("Everyone") will work appropriately.
796 C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S
Using the Roles Service with Windows Authentication
The Roles Service comes with a provider that integrates with Windows roles for Windows authentication: the WindowsTokenRoleProvider. This provider retrieves the Windows group membership information for the currently logged-on user and provides it in the same way for your application as you saw previously with the SqlRoleProvider. When using the WindowsTokenRoleProvider, you have to configure your application using Windows authentication and then configure the WindowsTokenRoleProvider as follows:
<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0"> <system.web>
<authentication mode="Windows"/>
<authorization>
<deny users="?" /> </authorization> <roleManager enabled="true"
cacheRolesInCookie="false"
defaultProvider="WindowsRoles">
<providers>
<add name="WindowsRoles" type="System.Web.Security.WindowsTokenRoleProvider" />
</providers>
</roleManager>
</system.web>
</configuration>
With this configuration in place, the user is authenticated through Windows authentication. The RoleManagerModule automatically creates an instance of RolePrincipal and associates it with the HttpContext.Current.User property. Therefore, you can use the RolePrincipal as follows—there is no difference compared to other Roles providers in terms of usage:
protected void Page_Load(object sender, EventArgs e)
{
if ((User != null) && (User.Identity.IsAuthenticated))
{
RolePrincipal rp = (RolePrincipal)User;
StringBuilder Info = new StringBuilder();
Info.AppendFormat("<h2>Welcome {0}!</h2>", User.Identity.Name);
Info.AppendFormat("<b>Provider: </b>{0}<br>", rp.ProviderName);
Info.AppendFormat("<b>Version: </b>{0}<br>", rp.Version);
Info.AppendFormat("<b>Expiration: </b>{0}<br>", rp.ExpireDate);
Info.AppendFormat("<b>Roles: </b><br>");
string[] Roles = rp.GetRoles(); foreach (string role in Roles)
{
if (!role.Equals(string.Empty)) Info.AppendFormat("-) {0}<br>", role);
}
LabelPrincipalInfo.Text = Info.ToString();
}
}
You can see the result of the previous code in Figure 23-6.
C H A P T E R 2 3 ■ A U T H O R I Z AT I O N A N D R O L E S |
797 |
Figure 23-6. Results of querying the RolePrincipal with Windows authentication
The provider-based architecture enables you to use Windows authentication with Windows groups without changing the inner logic of your application. Everything works the same as with the SqlRoleProvider. The same is true for the Membership API introduced in Chapter 21. When configuring another provider, you don’t have to change your code; however, you should have some programmatic authorization checks with hard-coded role names in your code, because the Windows groups include the domain qualifier and the custom roles do not. To avoid this, you can add functionality to your application that allows you to associate roles with permissions in either a database or a configuration file. The way you do this depends on the requirements of your application.
We suggest not using Windows groups for authorization in your application directly except for a few of the built-in groups such as the Administrators group. In most cases, it’s useful to define roles that are specific to your application. This is why:
•Windows groups other than the built-in groups depend on the name of the domain or machine on which they exist.
•In most cases, Windows groups in a domain are structured according to the organizational and network management requirements of the enterprise. Often these requirements do not map to the application requirements.
•Structuring application roles independently from the network groups makes your application more flexible and usable across multiple types of network structures.
A good example that introduces such a design is Windows SharePoint Services. SharePoint is a ready-to-use portal solution built on ASP.NET 1.x that can be used for free with Windows Server 2003. SharePoint includes prebuilt functionality for document libraries, meeting workspaces, and lists. You can use SharePoint for collaboratively working in teams—sharing documents, planning meetings and more.
