Design of the Security Application Block


The Security Application Block has several providers to make it easier for you to accomplish the tasks of authenticating users for an application, authorizing users for operations and tasks in an application, managing the list of roles to which a user belongs in an application, obtaining and persisting profile information for a user, and caching security and profile information for users in an application. The tasks that each provider accomplishes are defined by its interface. The interesting thing about each provider is which system it interfaces with. The block ships with providers for common systems, but it's expected that you will want to create your own to interface with your own systems. Each provider is intended to not only provide a level of flexibility for the application by abstracting it away from a particular security system, but to make it easy for you to accomplish the necessary tasks.

Unlike the Logging and Instrumentation Application Block, where many features existed that were dependent on the others, the Security Application Block has many features that can work completely independently of one another. The authentication features can be used regardless of whether or not the authorization, role, or profile features are used. Likewise, the authorization features can be used without using the AuthenticationProvider, RolesProvider, or ProfileProvider. However, while the providers are not dependent on each other, they are closely related. Thus, although it isn't necessary to use the AuthenticationProvider with the AuthorizationProvider, an application can reap many advantages by using both together. To help you understand this better, I will detail the design for each of the different areas covered by Enterprise Library's Security Application Block.

The Security Database

Before diving right into the specific design of the Security Application Block and how it provides capabilities to facilitate authentication, authorization, role management, profile management, and security credential caching in an enterprise application, it is a worthwhile exercise to learn about the features of the Security Database that ship with Enterprise Library. The reason for this is that many of the providers that ship with the Security Application Block depend on the existence of this database. Figure 7.1 illustrates the data model for the Security Database.

Figure 7.1. The Security Database


It is common for applications to maintain at least some security-related information in a relational database. Very often, this information is specific to the users who have been authorized for an application, the roles for those users, and profile information for those users. The Security Database is provided with tables for storing all of this data. Enterprise Library ships with an AuthenticationProvider, RolesProvider, and ProfileProvider that rely on the existence of this database. The Security Application Block includes a SQL script, SecurityDatabase.sql, that installs the necessary schema to a database named Security. You can change the name of the database where this exists or even install the schema in an existing database, but if you do, you will need to modify the configuration for the Security Database Administration Console so that it points to the proper database.

The Security Database Administration Console

Enterprise Library also ships with a tool for managing the data contained in this database. This tool, the Security Database Administration Console, lets an administrator create user identities and roles in the Security Database. It also allows the credentials for the users to be modified and for users to be associated with specific roles. Details on how to use and configure the Security Database Administration Console are covered later in the chapter in the section Section "Developing with the Security Application Block."

The UserRoleManager Class

The Security Database Administration Tool uses a utility class that ships with the Security Application Block for managing the Security Database. This class, UserRoleManager, encapsulates the stored procedures that perform activities like adding users, adding roles, and adding users to roles. It is really this class that has the dependency on Security Database schema and the fact that the Users and Roles tables must exist in the same database. More information about best practices and using UserRoleManager in your application is in the "Developing with the Security Application Block." sectionSection of this chapter.

Authentication

Authentication is the process of identifying an entity, typically through the use of credentials like user name and password. There are many different authentication mechanisms that can be leveraged for an application; however, if that mechanism is not properly implemented, vulnerabilities can be exposed. The primary design goal for authentication in the Security Application Block was to abstract an application away from the need to implement these particular authentication mechanics. Instead, the specifics behind any one authentication technology are handled by an Authentica tionProvider for that technology. Therefore, you can create applications to be indifferent as to the technology that is used to authenticate and instead concentrate on ensuring that authentication occurs at the proper places in the application. Figure 7.2 depicts the primary classes that the Enterprise Library uses for authentication.

Figure 7.2. Authentication in the Security Application Block


The AuthenticationProviderFactory Class and the IAuthenticationProvider Interface

The IAuthenticationProvider interface is the means by which this abstraction occurs. When an application needs to authenticate an entity, it does so by calling the Authenticate method of an AuthenticationProvider. If the authentication technology that is used needs to be changed, you can simply reconfigure the application with a different AuthenticationProvider.

Two constructs exist to help an application achieve this level of abstraction: the AuthenticationProviderFactory class and the IAuthenticationProvider interface. The AuthenticationProviderFactory class uses its configuration information to create an instance of an AuthenticationProvider. An AuthenticationFactory class (no "Provider" in the name) also exists that provides static methods that wrap around the public methods of the AuthenticationProviderFactory class. Either class can be used to obtain either a named instance of an AuthenticationProvider or one that is configured as the default AuthenticationProvider.

Both the AuthenticationFactory and AuthenticationProviderFactory classes expose a method named GetAuthenticationProvider that returns an instance of an AuthenticationProvider. An AuthenticationProvider is a class that implements the IAuthenticationProvider interface. The GetAuthenticationProvider method is overloaded two times: one overload expects a string that represents the name of an AuthenticationProvider that has been configured for the application, and the other overload does not expect any arguments. The first overload returns the named AuthenticationProvider, and the second returns the AuthenticationProvider that has been configured as the default AuthenticationProvider for the application.

The only method that a class must implement to support the IAuthenticationProvider interface is Authenticate. This method accepts an object that represents an entity's credentials (like NamePasswordCredentials) and, if the credentials are valid, will return a value of true for the method and an object that implements IIdentity as an output parameter.

An Identity object encapsulates information about the entity being authenticated. Identity objects contain the name of the entity, the authentication status, and an authentication type. The .NET Framework contains two Identity classes: WindowsIdentity and GenericIdentity. The Name property is the name of a Windows account for a WindowsIdentity and is typically the logged in user's name for a GenericIdentity. Custom Identity objects can be created; typically this is accomplished by subclassing the GenericIdentity.

The DbAuthenticationProvider Class

Enterprise Library provides an implementation of an AuthenticationProvider by way of the DbAuthenticationProvider. This class is used to authenticate a user based on the information stored in the Security Database that is provided with Enterprise Library. The DbAuthenticationProvider expects a NamePasswordCredentials instance and validates that the password for this user matches the password stored in the Users table for this user.

For security reasons, the passwords are stored as hashes in the database. This provider leverages the Data Access Application Block to connect to the proper database and the Cryptography Application Block to compare the hashed passwords. If the passwords match, a new GenericIdentity is created for the user with the AuthenticationType equal to the fully qualified name for the DbAuthenticationProvider + "." + the name of the AuthenticationProvider that was used. For example, if the name of the AuthenticationProvider was AuthProvider, the Identity returned would be this AuthenticationType:

Microsoft.Practices.EnterpriseLibrary.Security.Database. Authentication.DbAuthenticationProvider.AuthProvider


Note

Because identity is verified by comparing the hash of the password stored in the database, it is imperative that the Hash provider configured for an application's DbAuthenticationProvider matches the Hash provider that is used to store the user passwords in the database. If the Security Database Manager is used to configure the users and roles, the out-of-the-box setting is SHA1Managed. If the hashing algorithms do not match, then all authentication attempts will fail.


The NamePasswordCredential Class

Although it can also be used for other purposes, the NamePasswordCredential class is intended to facilitate passing a user name and password to the DbAuthenticationProvider. This class is very much like the System.Net.NetworkCredential class except that it doesn't have a field to represent the Domain to which a user might belong, and it possesses properties to represent a string as a byte array (a security best practice). The class has two constructors: one accepts the password as a string and the other accepts the password as a byte array. It also has three properties: Name, Password, and PasswordBytes.

Creating a Custom AuthenticationProvider

The Security Application Block is a little different than the other application blocks in Enterprise Library. While I have shown how to create custom providers for the other application blocks, you may never need to actually leverage them. The other application blocks are intended to supply the providers needed for most enterprise-level applications. I posit that although the Security Application Block provides many useful providers, it is fully expected that developers will need to create new providers to fit the needs of many enterprise applications.

It is possible that many enterprise applications will authenticate users by validating their credentials against a relational database. However, it is my experience that most enterprises use a different technology for authenticating users; most often users are stored in some type of LDAP store like Microsoft Active Directory.

The beauty of the Security Application Block is that it is provider based. While there is no issue with using the DbAuthenticate-Provider if that fits your scenario, you can simply use a different provider if it does not. Like the other custom providers that have been created in this book, there are two different ways to add a custom AuthenticationProvider. One way takes advantage of the CustomAuthenticationProviderData class and the hooks that exist for adding a class that implements the IAuthenticationProvider interface; the other way is to create a full-blown AuthenticationProvider with its own set of design-time features.

I ran into a situation where a custom AuthenticationProvider for Microsoft Active Directory would have been beneficial. Normally, most applications would not require such a provider because users can "automatically" authenticate to Active Directory for Windows Form applications when logging on to their machines or for Web applications using Integrated Windows Authentication in Internet Information Services (IIS). However, one company where I consulted needed to allow users to log into a Web application using HTML Forms authentication and authenticate the users against Active Directory. Integrated Windows Authentication could not be used because partners needed to log into the sites and there was little control over how a user would log into the client machine. To solve this problem, an AuthenticationProvider for Active Directory was created. The following steps show how to create this AuthenticationProvider for Microsoft Active Directory.

To create a custom AuthenticationProvider that takes advantage of the CustomAuthenticationProviderData class, the only thing you need to do is to create the AuthenticationProvider so that it implements the IAuthenticationProvider interface. The Initialize method can be used to read any configuration data that is needed; this configuration data will be contained in the CustomAuthenticationProviderData class.

The CustomAuthenticationProviderData class contains a Dictionary object named Extensions that can hold key/value pairs for the configuration data. Any specific data that is needed must be stored in this collection. The Authenticate method for this particular custom provider uses a private Login method that handles the specifics of dealing with Microsoft Active Directory. The full source code for this implementation is on this book's Web site.

When you have completed this, you can add the AuthenticationProvider as a custom AuthenticationProvider as shown in Figure 7.3.

Figure 7.3. Adding a Custom AuthenticationProvider


To design an AuthenticationProvider complete with a design-time interface, three more steps are needed. The CustomAuthenticationProviderData class won't be of use; a class that will hold the strongly typed configuration information necessary to leverage the specific authentication technology (in this case, Microsoft Active Directory) will be needed. Creating the data class for this specific provider is simple because I am using serverless binding and the RootDSE[1] to locate an Active Directory Server and authenticate against it. Therefore, no extra data beyond the user's name and password is needed. I have added the ADAuthenticationProviderData class for consistency, though. For complete information on how to create runtime configuration classes, see Chapter 1 and its accompanying source code.

[1] See http://msdn.microsoft.com/library/default.asp?url=/library/en-us/ad/ad/serverless_binding_and_rootdse.asp for information on serverless binding and the RootDSE.

The second step is to create a class that implements the IAuthenticationProvider interface and leverages the ADAuthenticationProviderData class. This new provider class is the same class that needs to be created using the previous method for creating a custom AuthenticationProvider. Listing 7.1 is the core method used by the Authenticate method for determining whether an entity can be authenticated using the supplied user name and password. The full source code is on the book's Web site.

Listing 7.1. Login Method for the ADAuthenticationProvider

    [C#]     private bool Login(string userName, string password)     {          bool result = false;          //Bind to rootDSE to get the Domain Name.          DirectoryEntry rootDSE = new DirectoryEntry("LDAP://RootDSE");          string domainName =               Convert.ToString               (rootDSE.Properties["defaultNamingContext"].Value);          rootDSE.Dispose();          //Bind to the domain root.          DirectoryEntry domainRoot =               new DirectoryEntry("LDAP://" + domainName);          string path = domainRoot.Path;          //Create the searcher, bound to the domain root.          DirectorySearcher dirSearch = new DirectorySearcher(domainRoot);          domainRoot.Dispose();          //Retrieve the 'cn' and adspath, which is returned by default.          dirSearch.PropertiesToLoad.Add("cn");          dirSearch.Filter = String.Format          ("(& (samaccountname={0})                 (objectCategory=person)(objectClass=user))",userName);          SearchResult searchResult = dirSearch.FindOne();          if (searchResult == null)          {               return false;          }          dirSearch.Dispose();          string adsPathToUser =               Convert.ToString(searchResult.Properties["adspath"][0]);          DirectoryEntry userEntry;          //'userName' is the sAMaccountName.          userEntry = new DirectoryEntry(adsPathToUser, userName, password);          if (userEntry != null)          {               try               {                    string commonName =                         Convert.ToString(userEntry.Properties["cn"].Value);                    if ( (commonName != null)                       && (! commonName.Equals(String.Empty)))                    {                       result = true;                    }          }          catch (Exception ex)          {               string thisError = ex.ToString();               result = false;          }          finally          {               userEntry.Dispose();          }     }     return result; } [Visual Basic] Private Function Login(ByVal userName As String, _                        ByVal password As String) As Boolean     Dim result As Boolean = False     'Bind to rootDSE to get the Domain Name.     Dim rootDSE As DirectoryEntry = _          New DirectoryEntry("LDAP://RootDSE")     Dim domainName As String = Convert.ToString(rootDSE.Properties _          ("defaultNamingContext").Value)     rootDSE.Dispose()     'Bind to the domain root.     Dim domainRoot As DirectoryEntry = _          New DirectoryEntry("LDAP://" + domainName)     Dim path As String = domainRoot.Path     'Create the searcher, bound to the domain root.     Dim dirSearch As DirectorySearcher = _          New DirectorySearcher(domainRoot)     domainRoot.Dispose()     'Retrieve the 'cn' and adspath, which is returned by default.     dirSearch.PropertiesToLoad.Add("cn")     dirSearch.Filter = String.Format _          ("(& (samaccountname={0}) _          (objectCategory=person)(objectClass=user))",userName)     Dim searchResult As SearchResult = dirSearch.FindOne()     If searchResult Is Nothing Then          Return False     End If     dirSearch.Dispose()     Dim adsPathToUser As String = _          Convert.ToString(searchResult.Properties("adspath")(0))     Dim userEntry As DirectoryEntry     'userName' is the sAMaccountName.     userEntry = New DirectoryEntry(adsPathToUser, userName, password)     If Not userEntry Is Nothing Then          Try               Dim commonName As String = _                    Convert.ToString _                         (userEntry.Properties("cn").Value)               If (Not commonName Is Nothing) AndAlso _                     ((Not commonName.Equals(String.Empty))) Then                    result = True               End If          Catch ex As Exception               Dim thisError As String = ex.ToString()               result = False          Finally               userEntry.Dispose()          End Try     End If     Return result End Function

The third step is to add the design-time classes for the ADAuthenticationProvider to make it easy to add and configure the provider in the Enterprise Library Configuration Tool. This step is not absolutely necessary; the XML can be modified directly in the configuration file (or other StorageProvider); however, creating design-time features for this class allows the Enterprise Library Configuration Tool to be used to easily configure this provider with the rest of the application settings. (For more information about creating design-time configuration classes with Enterprise Library, see Chapter 2.) The full source code for the ADAuthenticationProvider design-time classes is on the book's Web site. Figure 7.4 shows the ability to add the new AuthenticationProvider in the Enterprise Library Configuration Tool.[2]

[2] Thanks go to Steven Case for collaborating with me on the details for using RootDSE and serverless binding to authenticate against Active Directory.

Figure 7.4. Adding the Active Directory AuthenticationProvider


Caching Security Credentials

Many applications will not authenticate an entity for every request because it often carries too large of a performance overhead. Rather, authentication will often occur once and, for a period of time, any requests that require authentication will leverage the credentials used during the entity's original authentication. While this approach is less secure than authenticating for every request,[3] it greatly improves the performance of an application. A cost/benefits analysis of whether caching security credentials is appropriate as well as a determination of how long those credentials should stay in cache should be made on an application-by-application basis.

[3] Improving Web Application Security, p. 171, found at http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/ThreatCounter.asp.

While the Security Application Block can't help an organization make those determinations, it can help with the implementation. The Security Application Block ships with a Security Cache that can cache the security credentials used for authentication. The SecurityCacheProvider that comes with Enterprise Library leverages the Caching Application Block; therefore, that block must be configured for an application before this provider can be used.

Once an entity has been authenticated, the identity/principal for that entity can be cached. A token (typically a Guid) is returned as the identifier for that entity. That token can then be used for any future authentication request and the cache can be checked to validate identity instead of hitting the user store. Figure 7.5 highlights the primary classes that provide the Security Cache implementation.

Figure 7.5. The Security Application Block's Security Cache Design


The SecurityCacheFactory Class and the ISecurityCacheProvider Interface

The Security Cache's design is flexible enough to let you implement a different SecurityCacheProvider if you don't want to use the one that comes with Enterprise Library. To accomplish this you must develop a class that implements the ISecurityCacheProvider interface. The SecurityCacheProviderFactory serves as the public interface for retrieving an instance of one of these classes. And just as the AuthenticationFactory makes static methods available for retrieving AuthenticationProviders by encapsulating the AuthenticationProviderFactory class, so too does the SecurityCacheFactory provide static methods for retrieving ISecurityCacheProviders. You can use either the SecurityCacheFactory or the SecurityCacheProviderFactory class to obtain either the default or a named instance of a SecurityCacheProvider.

Both the SecurityCacheFactory and the SecurityCacheProviderFactory class expose a method named GetSecurityCacheProvider that returns an instance of a SecurityCacheProvider. A SecurityCache-Provider is a class that implements the ISecurityCacheProvider interface. The GetSecurityCacheProvider method is overloaded two times: one overload expects a string that represents the name of a SecurityCacheProvider that has been configured for the application, and the other overload does not expect any arguments. The first overload returns the named SecurityCacheProvider and the second returns the SecurityCacheProvider that has been configured as the default SecurityCacheProvider for the application.

The ISecurityCacheProvider interface defines methods that allow caching, retrieval, and expiration of identities, principals, and profiles. (For information on principals, see the section Section Authorization; for information on profiles, see the sectionSection Profile Management.) Table 7.1 lists the methods defined by the ISecurityCacheProvider interface.

Table 7.1. ISecurityCacheProvider's Interface Methods

Method

Description

GetIdentity

Gets an Identity object from the cache.

SaveIdentity

Caches an authenticated Identity object using an existing token.

ExpireIdentity

Deletes an Identity object from the cache.

GetPrincipal

Gets an existing Principal from the cache.

SavePrincipal

Caches an authenticated Principal object using an existing token.

ExpirePrincipal

Deletes an existing Principal object from the cache.

GetProfile

Gets an existing Profile object from the cache.

SaveProfile

Caches a Profile for an authenticated user using an existing token.

ExpireProfile

Deletes an existing Profile object from the cache.


All SaveXXX methods have two overloads: one takes just the object being cached, and the other takes the object and a Token. For example, the SaveIdentity method has two overloads: SaveIdentity(IIdentity) and SaveIdentity(IIdentity, Token).

The CachingStoreProvider Class

The CachingStoreProvider is Enterprise Library's implementation of the ISecurityCacheProvider interface. The CachingStoreProvider uses Enterprise Library's Caching Application Block by storing the security credentials in a CacheManager. (For information about Cache Managers and the Caching Application Block, see Chapter 4.)

Although three different facets for an entity can be cached, only one CacheManager is used. This is accomplished by encapsulating the Identity, Principal, and Profile information for a single entity in a SecurityCacheItem object. Thus, a single token can be used to represent an entity with any combination of the Identity, Principal, and/or Profile stored for that entity.

When any of the CachingStoreProvider's methods are called, the SecurityCacheItem is retrieved from the cache. If the item doesn't exist and the method is a Save, then a new SecurityCacheItem is created, added to the cache, and the appropriate property is set. If the item doesn't exist and the method is a Get, then null is returned; if it is an Expire method, then nothing happens.

When the item does exist, the Save, Get, and Expire methods do what you would expect them to dothey store, retrieve, or remove the value for the appropriate SecurityCacheItem property. SaveIdentity, for example, will store the Identity value supplied to the Identity property for the proper instance of a SecurityCacheItem. GetIdentity will retrieve that property's value, and ExpireIdentity will set it equal to null. Additionally, any of the Expire methods will completely remove a SecurityCacheItem from the cache if all three of the Identity, Principal, and Profile properties have been set to null.

The IToken Interface

Tokens are used to uniquely identify an entity. The Security Application Block does not enforce any specific type of token; however, a good token is one that is guaranteed to be unique. The only property you must implement to support the IToken interface is Value, which can return any string that can be used to uniquely identify an entity.

A fairly obvious implementation for such a token is the GuidToken.GuidToken is supplied by the Security Application Block, and this token implementation simply maintains a private Guid member variable. The default constructor will create a new Guid, and there is also a constructor that will set the internal variable equal to the Guid that is passed to it. The Value property simply returns Guid.ToString(). The CachingStoreProvider uses a GuidToken to uniquely identify a SecurityCacheItem object.

Creating a Custom SecurityCacheProvider

It is important to keep in mind that the Caching Application Block does not encrypt the data it stores when it caches in memory. If the Security Cache is configured to use a CacheManager that leverages the default backing store (NullBackingStore), an attacker that compromises the computer and accesses the memory of this process can access information stored in the cache.

You could create a Security Cache that encrypts the Security Cache items before adding them into the cache (one is on the book's Web site for this chapter). However, it is important to realize that you don't even need to use Enterprise Library's Caching Application Block to cache the security information if you're not a fan of that block.

The SecurityCacheProvider is an extension point in Enterprise Library's Security Application Block that allows alternative providers to be created and configured in an application. As an alternative to the provider that ships with Enterprise Library, I will create a SecurityCacheProvider that relies on the HttpRuntime.Cache and encrypts the Security Cache items before adding them to the cache.

If you've been with me from the beginning, this is going to seem like old hat by now. The first step that is needed to create any custom provider is to create the class that will hold the configuration data. For this provider, I have named that class HttpCachingStoreProviderData. The items needed to configure this provider are the sliding time expiration, absolute time expiration, and the name of the symmetric algorithm provider that is configured in the Cryptography Application Block. Unlike the Cache Manager in the Caching Application Block, the HttpRuntime.Cache cannot set both an absolute time and sliding time expiration for a single cache item. Therefore, logic exists in the provider to use the sliding time expiration if its value is greater than zero; otherwise, it uses the absolute time expiration. (For complete information on how to create runtime configuration data classes, see Chapter 1 and the source code that accompanies that chapter.)

The second step is to create a class that implements the IsecurityCacheProvider interface and leverages the data class created in the previous step. This is where the "real" work is. It is in this class that the implementation for how to implement the Get, Save-, and Expire-Identity, Principal, and Profile take place. The logic in this class differs from the CachingStoreProvider that ships with Enterprise Library in two ways.

  • It encrypts and decrypts the data as it inserts and gets the items from the cache.

  • It uses the HttpRuntime.Cache as its cache.

The majority of the code for this class is similar to the CachingStoreProvider code except for the two items just mentioned. In Listing 7.2, the HttpCachingStoreProvider class takes an item that has already been encrypted and stores it in the HttpRuntime.Cache.

Listing 7.2. Inserting a Cache Item with the HttpCachingStoreProvider

  [C#] private void InsertIntoCache(string key, byte[] item) {     HttpCachingStoreProviderData cacheStorageProviderData =               GetCacheStorageProviderData();     //Can't set both absolute time and sliding time on same item for     //HttpCache, so if sliding time exists, we'll pick that;     //otherwise, we'll use absolute.     DateTime setAbsoluteTime;     TimeSpan setSlidingTime;     int absoluteTime = cacheStorageProviderData.AbsoluteExpiration;     int slidingTime = cacheStorageProviderData.SlidingExpiration;     if (slidingTime > 0)     {          setSlidingTime = new TimeSpan          (0, 0, ConvertExpirationTimeToSeconds(slidingTime));          setAbsoluteTime = DateTime.MaxValue;     }     else     {          setSlidingTime = TimeSpan.Zero;          setAbsoluteTime = DateTime.Now.AddMinutes(absoluteTime);     }     WebCache.Cache cache = GetCache();     cache.Insert(key, item, null, setAbsoluteTime, setSlidingTime); } [Visual Basic] Private Sub InsertIntoCache(ByVal key As String, ByVal item As Byte())     Dim cacheStorageProviderData As HttpCachingStoreProviderData = _               GetCacheStorageProviderData()     'Can't set both absolute time and sliding time on same item for     'HttpCache, so if sliding time exists, we'll pick that;     'otherwise, we'll use absolute.     Dim setAbsoluteTime As DateTime     Dim setSlidingTime As TimeSpan     Dim absoluteTime As Integer = _               cacheStorageProviderData.AbsoluteExpiration     Dim slidingTime As Integer = _               cacheStorageProviderData.SlidingExpiration     If slidingTime > 0 Then          setSlidingTime = New TimeSpan _          (0, 0, ConvertExpirationTimeToSeconds(slidingTime))          setAbsoluteTime = DateTime.MaxValue     Else          setSlidingTime = TimeSpan.Zero          setAbsoluteTime = DateTime.Now.AddMinutes(absoluteTime)     End If     Dim cache As WebCache.Cache = GetCache()     cache.Insert(key, item, Nothing, setAbsoluteTime, setSlidingTime) End Sub

Lastly, add the design-time classes for the HttpCachingStoreProvider to make it easy to add and configure. There is nothing too special about how to do this: derive the HttpCachingStoreProviderNode from the SecurityCacheProviderNode and add properties for the symmetric algorithm provider, absolute time, and sliding time. (For more information about creating design-time configuration classes with Enterprise Library, see Chapter 2.)

The full source code for the HttpCachingStoreProvider design-time classes accompanies this chapter on the book's Web site. Figure 7.6 shows the result of adding the HttpCachingStoreProvider in the Enterprise Library Configuration Tool.

Figure 7.6. Configuring the HttpCachingStoreProvider


Authorization

Authorization is the process that an application uses to control access to resources and operations for an authenticated entity. Two methods of authorization are used in the Windows operating systems: access control list (ACL)-based authorization and role-based authorization.

For ACL-based authorization, discretionary access control lists (DACLs) can be attached to securable objects like files, directories, and registry keys. Authorization decisions are made by comparing the group memberships in a token to the contents of the ACL. The ACL model is great for dealing with static objects like files and registry keys, but it is difficult to use when well-defined persistent objects do not exist or business logic is needed to make an authorization decision.

For these situations, role-based authorization is preferred. Roles are associated with the actions and resources needed to perform specific activities. Administrators can then specify authority to those actions based on organizational structurethe roles or groups to which a user belongs. The concept behind role-based authorization is that users or groups are associated with specific roles that allow them to perform their jobs effectively. When a user or group is added to a role, it automatically inherits the security permissions for that role.

For example, let's assume that a portal site exists where different types of users have different levels of authorization. Let's also assume that business users can only read content; content administrators can read, add, or edit content; and site administrators have the same permissions as content administrators but they can also add, edit, and remove users as well as add, edit, and remove roles and assign users to roles.

Users will have different experiences when they visit the site based on their roles. Business users will have a read-only version of the site. Content administrators will have the ability to add content to the site, edit content on the site, and remove content from the site. Site administrators will have additional pages available that will include functionality for user and role management for the site. These kinds of rules are often hard-coded into applications. Having a role-based authorization system that is independent of the application makes the application more flexible because the authorization rules can be updated independently of the application. Because the portal site and database represent a single system in this enterprise, this example represents single-system (or simple) role-based authorization. Figure 7.7 depicts the varying experiences for the different types of users.

Figure 7.7. Role-Based Authorization Example


If, however, resources from another system were exposed through the portal site, then multisystem role-based authorization might be necessary. The upper right side of the portal site in Figure 7.7 has a table that shows a kind of sales dashboard. It is conceivable that this data could have come from a different system in the organization. If this were the case, then users of the portal site would need to be authorized for multiple systems: the portal site itself and other systems in the enterprise where pertinent data resides.

This scenario is complicated further when authorization needs must be more granular than simply accessing the system. Often authorization is specific as to the functions that a user is entitled to perform. For example, while all users may be authorized to view this dashboard, another role may exist that has permission to edit it. Furthermore, it is not unusual to have to temporarily grant "work on behalf" authorization to a user who does not exist in an authorized role or to allow a user a bit more permission than her role might allow.

A typical example, continuing the previous scenario, is when the site administrator goes on vacation. One possibility is for a different user to be temporarily assigned the role of site administrator during the vacation. However, if the site administrator has a lot of responsibilities, a common feature request is for those responsibilities to be divvied up to allow different users to perform the different operations. This type of multisystem authorization can get pretty complicated as role-based authorization is circumvented for direct operational-based authorization.

The authorization features in the Security Application Block will not make all complexities regarding authorization go away. In complex scenarios, issues still exist regarding the data a user is entitled to read or edit. While the Security Application Block doesn't address data entitlement, it does help with functional entitlement. Because the specific authorization rules are encapsulated by the AuthorizationProvider, you don't need to worry about the logic that is needed to authorize an entity for an operation.

For a simple version of this scenario, a provider that reads authorization rules from a database or file may be sufficient; for the most complex scenario, a provider that leverages Authorization Manager is probably needed. It doesn't matter to a developer, though; you can just call the AuthorizationProvider. Moreover, as authoritative rules change (like temporarily authorizing a user for an operation), only the configuration information needs to change; the application code does not. Figure 7.8 highlights the primary classes that create AuthorizationProviders in the Security Application Block.

Figure 7.8. Authorization in the Security Application Block


The AuthorizationProviderFactory Class and the IAuthorizationProvider Interface

Once again, factories and providers are the pattern that is used to make this abstraction available. When an application needs to authorize an entity for an operation, it does so by calling the Authorize method of an AuthorizationProvider. If the authorization store needs to be changed, you simply need to reconfigure the application with a different AuthorizationProvider.

The AuthorizationProviderFactory class uses its configuration information to create an instance of an AuthorizationProvider. An AuthorizationProvider is any class that implements the IAuthorizationProvider interface. Furthermore, in a similar way that AuthenticationFactory refines the AuthenticationProviderFactory with public static methods, the AuthorizationFactory refines the AuthorizationProviderFactory class. Either class can be used to obtain either a named instance of an AuthorizationProvider or one that is configured as the default AuthorizationProvider via the GetAuthorizationProvider method.

The GetAuthorizationProvider method is overloaded two times: one overload expects a string that represents the name of an AuthorizationProvider that has been configured for the application, and the other overload does not expect any arguments. The first overload returns the named AuthorizationProvider, and the second returns the AuthorizationProvider that has been configured as the default AuthorizationProvider for the application.

There is only one method that a class must implement to support the IAuthorizationProvider interface: Authorize. The Authorize method is designed to take advantage of Principals to determine if an entity passes an authorization check.

A Principal represents the security context the code is running under. It is really a combination of an Identity (discussed earlier in the Authentication sectionSection ) and a list of roles to which an entity belongs. The Authorize method expects a Principal object as its first parameter and a string that represents the name of the authorization rule to check as its second. The identity and role information contained in the Principal is run against the authorization rule, and if this information passes the authorization check, the method returns true. Otherwise, false is returned.

For example, let's assume the existence of an authorization rule named AdminFunctions that checks whether administrative functions in a site can be performed. The authorization rule could be expressed as simply as:

if (IsInRole("admins") ) then true else false;


If user Bob exists in the "admins" role but user Sally does not, then the Authorize(currentPrincipal, "AdminFunctions") method will return true for Bob and false for Sally. Consequently, Bob will be given access to administrative functions and Sally will not.

The AuthorizationRuleProvider Class

Enterprise Library ships with an AuthorizationProvider that allows easy creation and checking of rules. The AuthorizationRuleProvider uses a specialized grammar to evaluate expressions using the roles and Identity that exist in the Principal object that is passed to it. For example, the rule given in the previous example would be expressed as R:admins for the AuthorizationRuleProvider. The provider will interpret this rule expression as IsInRole("admins"). Authorization rules for the AuthorizationRuleProvider are kept in the configured StorageProvider with the rest of the settings for the Security Application Block.

Figure 7.9 highlights the classes used to implement the specialized grammar for AuthorizationRuleProvider.

Figure 7.9. Classes Used for the AuthorizationRuleProvider


Rule Expressions

The AuthorizationRuleProvider uses rule expressions to determine whether an entity is authorized to perform a specific operation. A rule expression is the aggregate of different Boolean expressions and returns a single Boolean result. To achieve this, the AuthorizationRuleProvider leverages the many subclasses of the abstract BooleanExpression base class. Figure 7.9 shows these classes.

A BooleanExpression class represents an operator, operand, or expression that returns a Boolean value. All of the expression classes used by AuthorizationRuleProvider must derive either directly or indirectly from this class. BooleanExpression defines a single method that must be overridden:

bool Evaluate(IPrincipal principal)


Table 7.2 lists the available operators and expressions.

An example of a complete authorization rule expression for "Joe or anyone who is part of the Admin group and not part of the Managers group and not Sally (because she will be let go next week)" would be expressed as:

I:Joe OR (R:Admins AND (NOT R:Managers) AND (NOT I:Sally))


This works because the AuthorizationRuleProvider is able to apply an entity's Principal against any representation using this grammar and arrive at a single Boolean value that indicates whether authorization is successful or not. It does this by first parsing the expression into representative tokens using the token syntax listed in Table 7.2. The lexical analyzer used to match the tokens also tokenizes the left and right parentheses characters, a quoted string, invalid characters, and the end of file.

All lexical analyzers are made up of finite state machines, and the Security Application Block's lexical analyzer is no different. If the rule in the previous example were run for Sally, the Parser would break the expression into the nineteen tokens {I:, Joe, OR, (, R:, Admins, AND, (, NOT, R:, Managers, ), AND, (, NOT, I:, Sally, ), and )}, and it would be evaluated using the finite state machine depicted in Figure 7.10. The final result would be false and Sally would not be granted access.

Table 7.2. Classes Derived from BooleanExpression for Use by the Lexical Analyzer

BooleanExpression Subclass

Token Syntax

Description

Example (and Translation)

WordExpression

- -

Represents a word value such as a role name or identity name.

Joe

IdentityExpression

I:

Represents an expression that contains the name of an IIdentity. Uses a WordExpression for the value of the Identity.

I:Joe (Identity is Joe)

RoleExpression

R:

Represents an expression that contains the name of a role. Uses a Word-Expression for the value of the role.

R:Admins (is in Admins Role)

AnonymousExpression

?

Derives from Word-Expression and returns the negation of an IIdentity object's IsAuthenticated property.

I:? is anonymous user)

AnyExpression

*

Derives from Word-Expression and evaluates to true for any specified role or Identity.

R:* OR I:* (in any role or any Identity)

NotOperator

NOT

Represents the logical negation operator that negates its operand. Returns true if and only if its operand is false.

NOT R:Admins (not in Admins role)

AndOperator

AND

Represents the logical AND operator for its contained left and right expressions. Only evaluates its second expression if the first expression evaluates to true.

R:Admins AND I:Joe (in Admins role and Identity is Joe)

OrOperator

OR

Derives from AndOperator. Represents the logical -OR operator for its contained left and right expressions. Only evaluates its second expression if the first expression evaluates to false.

R:Admins OR I:Joe (in Admins role or Identity is Joe)


Figure 7.10. AuthorizationRuleProvider Finite State Machine


Enterprise Library's Authorization Manager Provider

The AuthorizationRuleProvider is very useful for specifying functional entitlement (that is, what operations certain entities are allowed to perform) in an application. If an application can express all its authorization rules using the grammar shown in the previous section, then the AuthorizationRuleProvider should be sufficient. However, very often authorization to perform an activity cannot be based solely on identities and roles; often more complex relationships that exist between the roles, users, and activities must be considered for an authorization request. Additionally, it is very common for authorization decisions to consider data that is not known until runtime. The Microsoft Authorization Manager (aka AzMan) has been designed to not only consider the complex, hierarchical relationships that can exist between groups, roles, users, and tasks, but to also take the results of VBScript or Jscript into account, thereby allowing it to incorporate data entitlement (that is, what data an entity is entitled to view, change, or delete) as well as functional entitlement.

I am not going to describe Authorization Manager in depth. To do so would require at least another chapter and I probably wouldn't do it justice at that. I will, however, try to provide enough information for you to understand what Authorization Manager is, how it works, and how Enterprise Library's AzManAuthorizationManager uses it. For a more in-depth look at Authorization Manager, read the information about it on Microsoft TechNet[4] and Dave McPherson's article entitled "Role-Based Access Control for Multi-tier Applications Using Authorization Manager."[5] If you already understand the concepts behind Authorization Manager and how it is intended to be used, feel free to skip this next section.

[4] Found at www.microsoft.com/technet/prodtechnol/windowsserver2003/ library/ServerHelp/1b4de9c6-4df9-4b5a-83e9-fb8d49772378.mspx.

[5] Found at www.microsoft.com/technet/prodtechnol/windowsserver2003/technologies/management/athmanwp.mspx.

Authorization Manager (AzMan)

Windows Server 2003 introduced Authorization Manager as a new capability for applications to leverage for managing and administering role-base authorization. The administration capabilities have also been ported to Windows 2000 SP4 and Windows XP. There are two primary users of AzMan: developers and administrators. Developers leverage AzMan's API to provide role-based and rule-based authorization for the applications they are developing; administrators leverage AzMan's Microsoft Management Console (MMC) snap-in to configure the various constructs that make up these roles and rules. The authorization information can be stored in either Microsoft Active Directory or an XML file; however, the developer does not need to be aware of the location for the authorization store. This is simply an administrative decision that needs to be made. It is important, however, for both developers and administrators to understand the various concepts in AzMan and how these concepts can be used to provide flexible authorization policies.

There are several constructs that define authorization policies in Authorization Manager. Two of these constructs, operations and tasks, fall into the category of functions that can be performed by an entity. Operations are more developer-centric because they are very granular and often times equate to specific functions that may exist in code. Tasks are more administrative-centric as they are the aggregation of multiple operations and other tasks into a unit of work.

For example, in the portal site scenario used earlier in this chapter, certain users are allowed to view a dashboard control while other users are not. Viewing the dashboard can be thought of as the task ViewDashboard. This task consists of several operations: GeTDashboardInfo, RenderDashboardInfo, and SetDashboardEditControls. Furthermore, higher-level tasks can also exist that might leverage the other tasks. For instance, a task might exist to show the administrative tab and functions in the site; this task might be the combination of all the tasks that are considered to be administrative tasks, such as manage user accounts, remove accounts, assign users to roles, and so on.

Tasks are also extremely powerful because of the ability to associate a VBScript or Jscript with the task. This authorization script can take advantage of runtime parameters and values to determine whether permission to do the task should be granted. This allows tasks in Authorization Manager to reach beyond static, functional entitlement rules into the realm of dynamic, functional data entitlement. For example, an employee portal site might contain an authorization rule such that an employee can only create a purchase order if the amount is less than $1,000. The amount of the purchase order that contributes to this authorization check is dynamic; its value is not known until the application is running. The VBScript in Listing 7.3 is an example of such an authorization rule.

Listing 7.3. Sample Authorization Rule in Authorization Manager

Dim poAmount AzBizRuleContext.BusinessRuleResult = FALSE Amount = AzBizRuleContext.GetParameter("POAmount") if poAmount < 1000 then AzBizRuleContext.BusinessRuleResult = TRUE

Table 7.3. Authorization Manager Terminology

Responsibility

Term

Definition

Functional and Data Entitlement

Operation

A low-level permission used to identify access permission to a specific method. Several operations may be required to perform a meaningful task and are therefore often not exposed or meaningful to administrators.

Task

A collection of operations. The purpose of the task is to determine which operations are required to do some unit of work that is meaningful to administrators.

Authorization Script# Scripts that are attached to a task object that is run at the time of the access request. It can use information that is only available at runtime, such as "dollar amount," to make an authorization decision.

Grouping Entities and Resources

Role

The set of permissions that users must have to be able to do their job.

Scope

A collection of objects or resources with a distinct authorization policy. Applications can use the scope as necessary to group resources.

Application Group

Groups that are applicable only to an authorization store, an application in an authorization store, or a scope in an application.


Other concepts in Authorization Manager are associated with the entities that have access to the application, the relationship the entities have with one another, and the relationship those entities have with the tasks and operations they can perform. Table 7.3 defines the terminology used when working with AzMan and categorizes which are related to entitlement and which are related to grouping entities and resources.

As you might expect, the concept of a role exists in Authorization Manager. Roles are used to group users together into categories that reflect the permissions those users need to do their jobs. A role definition consists of the tasks and operations that users in that role can perform as well as other, lower-level roles. For example, business users in the portal site may have very limited permissions; they may only be able to view the site in read-only mode and may not be able to view all controls that exist. A content administrator will probably have all the permissions that a business user has plus additional permissions to add, edit, and remove content.

Figure 7.11 illustrates the definition for the content administrator with the business user as a lower-level role and permission to execute an additional ViewDashboard task. The site administrator can be defined with the content administrator as a lower-level role and a permission to execute additional tasks and operations. This allows for a hierarchical model of roles and eases the burden of assigning multiple tasks to every role in the hierarchy.

Figure 7.11. Authorization Manager Role Definition Example


Authorization Manager also has the concept of scope, which allows application resources to be grouped together so that different authorization policies can be specified per collection. Scope is very useful when there are specific areas in an application that must have specialized permission. For example, registering as a "member" for a business-to-consumer site will grant authorization for special areas in a Web site that nonmembers are not allowed. This membership area can often be represented as a subsite (e.g., http://myb2csite/membersonly) or a directory (e.g., C:\myb2csite\membersonly). A scope can be used to represent this membership area and tasks, operations, and roles that were defined at a global level for the application.

The configuration in Figure 7.11 includes two scopes: Members Only Area and Work on behalf of Joe. This second scope is meant to include tasks so a user can perform work on behalf of Joe. In this scenario, Joe is a site administrator. When Joe takes a vacation, the functions that Joe performs still need to continuethe site cannot be allowed to become dormant just because Joe is on vacation. So, a separate scope exists that allows roles to be defined that contain a subset of the work that Joe needs to perform as site administrator. When Joe goes on vacation, users can be added to these roles to "work on behalf of Joe" while he is gone. When Joe returns from vacation, the users can be removed from these roles.

The last Authorization Manager concept that I will mention is that of application groups. Application groups work like Windows security groups except that they not only contain a list of members in the group, but they also have a list of nonmembers. The nonmembers list takes precedence over the members list. If a user appears in both lists, that user will not be granted membership. Application groups can also be assigned as members in a role. Because application groups can either be global throughout the entire authorization store or defined in a specific scope, they can be assigned to any role for any application in the authorization store.

Developing Against Authorization Manager.

Authorization Manager is a set of COM-based runtime interfaces. The interop assembly, Microsoft. Interop.Security.AzRoles.dll, can be used to access the COM-based interfaces through managed code.[6] These interfaces are used to obtain a connection to the authorization store, to obtain roles, and to determine permission for a given operation.

[6] It is important to remember that the AzManAuthorizationProvider uses this interop assembly, because it is necessary to deploy the interop assembly when leveraging AzManAuthorizationProvider in an application and to ensure that the Enterprise Library Configuration Tool can access the assembly when configuring the AzManAuthorizationProvider.

The AzAuthorizationStoreClass' Initialize method must be called to initialize the Authorization Manager store. After this, the AzAuthorizationStoreClass' OpenApplication method can be called to bind to a specific application in the AzMan store. An AzMan context must be created for a client to connect to an application. The best and fastest method for accomplishing this is via the IAzApplication's InitializeContextFromToken method. Once the client context is created, role and authorization information can be retrieved.

To obtain role information, the IAzClientContext's Getroles methods can be called. To obtain authorization information for an operation, first the scope in which the user is making the request must be determined. Then IAzClientContext's AccessCheck can be called with an identifier to be used for auditing purposes, the relevant scope, the operations for which authorization is being requested, and the authorization script parameters and values. All of the arguments are evaluated against the information stored in AzMan, and an array of integers (signifying Boolean values) is returned. Each item in the array indicates whether authorization for a particular operation is granted.

AzManAuthorizationProvider.

Most of these steps to obtain role information are the same steps taken when Enterprise Library's AzManAuthorizationProvider is used. Since the AzManAuthorizationProvider subscribes to the Provider pattern and implements the IAuthorization Provider interface, you only need to understand one method to use it: Authorize.

That is a good thing for all the reasons that the Provider pattern is good. For example, if an application that subscribes to this pattern needs to switch from the AuthorizationRuleProvider to the AzManAuthorizationProvider, no code needs to changeat least in theory. In reality, there is a caveat that must be understood for this to actually be true. The caveat is that the authorization function name [7] must begin with an O: (the letter O and a colon) for an Authorization Manager operation. If the rule does not begin with an O:, then the AzManAuthorizationProvider will assume the request to be for an Authorization Manager task.

[7] What I am calling an authorization function name is actually the value for the parameter named context in the Authorize method. To avoid confusion with Authorization Manager's client context, it seemed appropriate to refer to it as authorization function name.

Regardless of whether the authorization request is for an operation or a task, an audit identifier is always created from a combination of the AuditIdentifierPrefix that is set through configuration, the Identity.Name of the principal for whom the authorization request is intended, and the name of the authorization function. Using the configuration shown in Figure 7.12, the auditIdentifier of an authorization request for Joe to access the operation named GetdashboardInfo would be pssAuditId-Joe:O:GetDashboardInfo.

Figure 7.12. Enterprise Library's AzManAuthorizationProvider


When the AzManAuthorizationProvider checks access for an operation, it first gets the client context via the IAzApplication's InitializeContextFromToken method. It then obtains the scope for which the AzManAuthorizationProvider has been configured. In Figure 7.12, no scope was configured, so the value for the scope parameter would be null. These values, along with the auditIdentifier and the name of the authorization function (the OperationID) are passed to the IAzClientContext's AccessCheck method to determine authorization.

A similar process occurs when the AzManAuthorizationProvider checks access for a task. The primary difference is that an array of OperationIDs for the operations that make up a task are first retrieved by leveraging the Authorization Manager API. This array of OperationIDs is used instead of the single OperationID in the call to the IAzClientContext's AccessCheck method.

Notice that no script parameters or dynamic, runtime values are passed to the AccessCheck method. The AzManAuthorizationProvider doesn't support it. However, the AzManProvider in the previous Authorization and Profile Application Block supported this via the CheckAccess method. To support passing runtime values, the Authorize method would need to accept additional parameters to hold these values or the AzManAuthorizationProvider would need to provide another method for setting them. I can see that providing this functionality would be meaningless to the AuthorizationRuleProvider; however, the argument could be made that adding the ability for the AuthorizationRuleProvider to consider runtime values and parameters would be an excellent enhancement. It is a shame that the support for this feature was lost because it is extremely powerful.

Extending the AuthorizationProvider

AuthorizationProviders allow for another extension point in the Security Application Block. Custom AuthorizationProviders can be created to authorize against different authorization stores and in different ways than the AuthorizationRuleProvider and the AzManAuthorizationProvider. Because there isn't anything too different about what you need to do to create an AuthorizationProvider that makes it much different from creating any other provider in Enterprise Library, I'm not going to show you how to create a custom AuthorizationProvider. (For a list of the tasks, see Chapters 1 and 2.) Rather, I'm going to show another way to add support for authorization by adding declarative security capabilities to the Security Application Block.

Using an AuthorizationProvider with Enterprise Library means that eventually there needs to be code that calls the AuthorizationProvider's Authorize method. When you write code to call Authorize, you are performing what is known as imperative security checks, which is akin to performing role-based security checks by calling the Principal object's IsInRole method directly. However, attributes can be decorated around a method to perform this check on your behalf. Instead of explicitly calling principal.IsInRole("Manager") in a method, the method could be decorated with a PrincipalPermission like that shown in Listing 7.4.

Listing 7.4. Example of Declarative Security

[C#] [PrincipalPermission (SecurityAction.Demand, Role = "Managers")] public static void MyMethod() {      //Do some stuff. } [Visual Basic] <PrincipalPermission (SecurityAction.Demand, Role := "Managers")> _ Public Shared Sub MyMethod()      'Do some stuff. End Sub

Because this method is attributed with the PrincipalPermissionAttribute, a security check will automatically be performed to determine whether the current user is in the Managers role. If the user is not, a Secu rityException will be thrown; otherwise, access will be granted and the code will be allowed to execute.

Just because AuthorizationProviders are used to perform authorization with the Security Application Block does not mean that support for declarative security cannot be provided. However, it will take some extra code to allow for it. This section shows you how to create custom attributes to allow for declarative security against AuthorizationProviders. However, there are a couple of caveats that you should know about first.

  • To create a custom attribute so that it can be leveraged by multiple applications, the assembly must be strongly named. By itself, this is not too big a deal; however, for an assembly to be strongly named, every assembly on which it depends must also be strongly named. For the custom attribute created in Listing 7.5 and shown in Listing 7.7, that includes the assemblies for the security, configuration, and common blocks.

  • The assembly for the custom attribute must be installed in the Global Assembly Cache (GAC) for other applications to use it.

Okay, now that you're aware of these caveats, let's get started. The first step is to create a class that implements IPermission and keeps track of three main properties: whether the user is already authenticated, the name of the AuthorizationProvider, and the rule to use to determine authorization. The most interesting method in this class is the Demand method, because this is where the determination is made as to whether the user is authorized against the supplied rule or not.

Listing 7.5. Demand Method for the Custom Permission Class

[C#] public void Demand() {     IPrincipal principal = Thread.CurrentPrincipal;     if (principal == null)     {          throw new          SecurityException("Security_AuthProviderPermission",               base.GetType(), this.ToXml().ToString());     }     if (this.arrProviderInfo != null)     {          int num = this.arrProviderInfo.Length;          bool flag = false;          for (int num2 = 0; num2 < num; num2++)          {               if (this.arrProviderInfo[num2].authenticated)               {               IAuthorizationProvider authorizationProvider;               if ((this.arrProviderInfo[num2].providerName == null)                    || (this.arrProviderInfo[num2].providerName.Equals                          (String.Empty)))               authorizationProvider =                    AuthorizationFactory.GetAuthorizationProvider();               else                    authorizationProvider =                         AuthorizationFactory.GetAuthorizationProvider                         (this.arrProviderInfo[num2].providerName);                    if ((this.arrProviderInfo[num2].rule != null) &&                         !authorizationProvider.Authorize                         (principal,this.arrProviderInfo[num2].rule))                    {                         throw new SecurityException                          ("Security_AuthProviderPermission",                              typeof(AuthorizationProviderPermission));                    }                    flag = true;                    break;               }               flag = true;               break;          }          if (!flag)          {               throw new SecurityException                     ("Security_AuthProviderPermission",                         typeof(AuthorizationProviderPermission));          }     } } [Visual Basic] Public Sub Demand()     Dim principal As IPrincipal = Thread.CurrentPrincipal     If principal Is Nothing Then _          Throw New _               SecurityException _                    ("Security_AuthorizationProviderPermission",_                    MyBase.GetType(), Me.ToXml().ToString())     End If     If Not Me.arrProviderInfo Is Nothing Then          Dim num As Integer = Me.arrProviderInfo.Length          Dim flag As Boolean = False          For num2 As Integer = 0 To num - 1               If Me.arrProviderInfo(num2).authenticated Then                    Dim authorizationProvider As _                         IAuthorizationProvider                    If (Me.arrProviderInfo(num2).providerName Is _                    Nothing) OrElse _                   (Me.arrProviderInfo(num2).providerName.Equals _                   (String.Empty)) Then                         authorizationProvider = _                         AuthorizationFactory.GetAuthorizationProvider()                    Else                         authorizationProvider = _                         AuthorizationFactory.GetAuthorizationProvider                              (Me.arrProviderInfo(num2).providerName)                    End If                    If (Not Me.arrProviderInfo(num2).rule Is Nothing)                    AndAlso (Not authorizationProvider.Authorize _                   (principal,Me.arrProviderInfo(num2).rule)) Then                         Throw New SecurityException _                         ("Security_AuthorizationProviderPermission",                         GetType(AuthorizationProviderPermission))                    End If                    flag = True                    Exit For               End If               flag = True               Exit For          Next num2          If (Not flag) Then               Throw New SecurityException _               ("Security_AuthorizationProviderPermission", _               GetType(AuthorizationProviderPermission))          End If     End If End Sub

The full source code for the AuthorizationProviderPermission class is on the book's Web site.

After the Permission class is created, the Attribute class must be created. This class is easier to implement; it is more of a wrapper on top of the Permission class. It is just a matter of creating a single constructor that accepts a SecurityAction, a CreatePermission method that creates a new instance of the class highlighted in the previous step, and the three properties that contain the information that will need to be set: Authenticated, ProviderName, and Rule.

When this attribute has been successfully compiled, unit-tested, strong-named, and installed in the GAC, it is ready for use. Code that previously looked like Listing 7.6 can instead be written as Listing 7.7.

Listing 7.6. Imperative Security Using AuthorizationProviders

[C#] public static void MyMethod() {     bool bAuthorized = false;     IAuthorizationProvider authProvider =          AuthorizationFactory.GetAuthorizationProvider();     bAuthorized =     authProvider.Authorize(HttpContext.Current.User,"ViewDashboard");     if (bAuthorized)          //Do some stuff. } [Visual Basic] Public Shared Sub MyMethod()     Dim bAuthorized As Boolean = False     Dim authProvider As IAuthorizationProvider = _          AuthorizationFactory.GetAuthorizationProvider()     bAuthorized = _     authProvider.Authorize(HttpContext.Current.User, "ViewDashboard")     If (bAuthorized) Then           'Do some stuff. End Sub

Listing 7.7. Declarative Security Using AuthorizationProviders

[C#] [AuthorizationProviderPermission (SecurityAction.Demand, Rule = "ViewDashboard")] public static void MyMethod() {     //Do some stuff. } [Visual Basic] <AuthorizationProviderPermission (SecurityAction.Demand, Rule = "ViewDashboard")> _ Public Shared Sub MyMethod()     'Do some stuff. End Sub

Role Management

The previous section detailed that Enterprise Library applies roles and identities against predefined rules to determine if an entity is authorized to perform an operation. Enterprise Library's authorization features do not, however, include managing the roles in any way. An application must first get the roles for an entity before it is able to authorize that entity for an operation. This is the responsibility of the RolesProviderFactory and IRolesProvider. Figure 7.13 depicts the classes used to provide role management with the Security Application Block.

Figure 7.13. Role Management in the Security Application Block


The RolesProviderFactory and the IRolesProvider Interface

The RolesProviderFactory class uses its configuration information to create an instance of a class that implements the IRolesProvider interface. By now you're probably getting the gist that for every ProviderFactory in Enterprise Library there is a corresponding Factory class that provides static methods to wrap around it. For the RolesProviderFactory, that class is the RolesFactory. Either class can be used to obtain a named instance of an IRolesProvider or the one that is configured as the default.

The GeTRolesProvider method is exposed by both of these classes and is overloaded two times: one overload expects a string that represents the name of a RolesProvider that has been configured for the application, and the other overload does not expect any arguments. The first overload returns the named RolesProvider and the second returns the RolesProvider that has been configured as the default RolesProvider for the application.

There is a single method that a class must implement to support the IRolesProvider interface: Getroles. The Getroles method accepts an object that implements IIdentity and returns an IPrincipal. Typically, the Identity will be the same one that was retrieved from an AuthenticationProvider's Authenticate method and the Principal will be passed to an AuthorizationProvider's Authorize method.

The SecurityRolesProvider Class

The SecurityRolesProvider is an abstract base class that is intended to make it easier for developers to create RolesProvider. The SecurityRolesProvider implements the IRolesProvider interface and creates and returns a GenericPrincipal for the Getroles method. It uses an abstract method named CollectAllUserRoles that must return an array of strings to represent the roles for the specified Identity. Thus, a concrete RolesProvider only needs to subclass the SecurityRolesProvider class and override the CollectAllUserRoles with the code needed for the user store it represents. The Security Application Block ships with two such providers: the DbRolesProvider and AdRolesProvider.

The DbRolesProvider retrieves the roles for a user from the Security Database that comes with the Security Application Block. The DbRolesProvider gets the name of the DatabaseInstance from the configuration data that is stored for the Security Application Block. It uses this in its CollectAllUserRoles method to create a UserRoleManager. It calls UserRoleManager's GetUserRoles method to retrieve the roles; since GetUserRoles returns a DataSet, the DbRolesProvider first converts the DataSet to an array of strings before returning to the caller.

Enterprise Library also supplies the AdRolesProvider for getting an entity's roles from Active Directory. Its inner workings are a bit more complex than DbRolesProvider, but this complexity is mostly hidden from those applications consuming it. The AdRolesProvider must be configured with the name of the server hosting the Active Directory Service (Server), the provider type name for Active Directory (ProviderType), the partition in the Active Directory that contains the user objects/data (UserPartition), and the name of a field in the schema that can be used to find the current account name (AccountName).

It uses the first three values to bind to Active Directory and the last one to determine which field to use to search for the user. For example, if the value for this data is:

MyServer:389", "LDAP", "CN=CompanyUsers,O=OrgDept,C=US", and "CN"

the AdRolesProvider will bind to Active Directory with

LDAP://MyServer:389/CN=CompanyUsers,O=OrgDept,C=US

and search for the entry where CN=<name of Identity>. The AdRolesProvider then uses the names of the groups to which the user belongs for the list of roles that it returns.

Creating a Custom RolesProvider

If you used the previous Authorization and Profile Application Block and leveraged the AzManProvider, you may have also used its ability to get the roles that existed in Authorization Manager. Unfortunately, that feature did not make its way into Enterprise Library's Security Application Block. Roles and authorization were split into two separate providers, and an AzManRolesProvider does not exist. This may be, however, an important RolesProvider to have if you are using the AzMan AuthorizationProvider for authorization. It is useful to be able to get the roles for the users of an application from the same store that is used to authorize those users.

Therefore, I have migrated this feature from the previous Authorization and Profile Application Block and created a custom RolesProvider for getting the roles from Authorization Manager. First, I created a class that holds the configuration data. This class, the AzManRolesProviderData class, contains member variables to maintain information like the location of the AzMan authorization store, the name of the application, and the scope in the application (if any).

Then I created the actual RolesProvider, AzManRolesProvider, so that it derives from the SecurityRolesProvider base class. The base class does most of the work for returning the roles to the consumers of this provider; I only needed to create two methods: Initialize and CollectAllUserRoles. The Initialize method is needed to read the configuration settings; the CollectAllUserRoles method does the work of getting the roles from the specific user store and returning them as a string collection. Listing 7.8 shows the CollectAllUserRoles method for the AzManRolesProvider.

Listing 7.8. AzManRolesProvider's CollectAllUserRoles Method

[C#] protected override string[] CollectAllUserRoles(IIdentity userIdentity) {     AzManRolesProviderData azManRolesData = (AzManRolesProviderData)          securityConfigurationView.GetRolesProviderData          (ConfigurationName);     string applicationName = azManRolesData.Application;     try     {          IAzApplication azApp = null;          IAzClientContext clientCtx = GetClientContext(azManRolesData,                                        userIdentity,                                        applicationName,                                        out azApp);          IAzScopes scopes = azApp.Scopes;          StringCollection rolesCollection = new StringCollection();          // Get the application's roles.          Array varRoles =               clientCtx.GetRoles(azManRolesData.Scope) as Array;          Debug.Assert(varRoles != null);          for(Int32 index =0; index < varRoles.Length; index++)          {               rolesCollection.Add((String)varRoles.GetValue(index));          }          string[] arrRoles = new string[rolesCollection.Count];          rolesCollection.CopyTo(arrRoles,0);          return  arrRoles;     }     catch (COMException comEx)     {          throw new SecurityException(comEx.Message, comEx);     } } [Visual Basic] Protected Overrides Function CollectAllUserRoles _                (ByVal userIdentity As IIdentity) As String()     Dim azManRolesData As AzManRolesProviderData = _           (AzManRolesProviderData) _           securityConfigurationView.GetRolesProviderData _           (ConfigurationName)     Dim applicationName As String = azManRolesData.Application     Try          Dim azApp As IAzApplication = Nothing          Dim clientCtx As IAzClientContext = GetClientContext( _                                                  azManRolesData, _                                                  userIdentity, _                                                  applicationName, _                                                  azApp)          Dim scopes As IAzScopes = azApp.Scopes          Dim rolesCollection As StringCollection = _               New StringCollection()          ' Get the application's roles.          Dim varRoles As Array = IIf(TypeOf _               clientCtx.GetRoles(azManRolesData.Scope) Is Array, _               CType(clientCtx.GetRoles(azManRolesData.Scope), Array),               CType(Nothing, Array))          Debug.Assert(Not varRoles Is Nothing)          For index As Int32 = 0 To varRoles.Length - 1                rolesCollection.Add _                (CType(varRoles.GetValue(index), String))          Next index          Dim arrRoles As String() =  _               New String(rolesCollection.Count - 1) {}          rolesCollection.CopyTo(arrRoles,0)          Return arrRoles     Catch comEx As COMException          Throw New SecurityException(comEx.Message, comEx)     End Try End Function End

Finally, I added the design-time classes needed to configure this provider with the Enterprise Library Configuration Tool. I derived my AzManRolesProviderNode from the RolesProviderNode and added properties for the store location, application name, and scope. (For more information about creating providers with Enterprise Library, see Chapters 1 and 2.) The full source code for the AzManRolesProvider is on this book's Web site. Figure 7.14 is the result of adding and configuring the AzManRolesProvider to an application's configuration data.

Figure 7.14. Configuring the AzManRolesProvider


Profile Management

Authenticating and authorizing a user through the use of identities, roles, and rules is necessary to secure an application. Combining these three constructs also allows a level of personalization in an application, as one user may have a completely different experience than another user based on the way these constructs are configured in the application. The Authorization section showed how a business user, content administrator, and site administrator can all have different experiences when navigating a portal site because of their access to different features. True personalization, however, usually accounts for more than this; it must usually consider other attri-butes that are associated with a user's profile.

A profile can consist of anything that an application deems important enough to tie to an entity. Many times this information is kept permanently for a user. Common examples are a user's address and preferences (like favorite color and stock symbols). However, profiles can also be used to store information related to a specific session. For example, items in a user's shopping cart from a consumer site or a list of documents to download from a portal site can often be kept in a user's profile.

While managing profiles isn't exactly related to security, it is related to user management. Its inclusion in the Security Application Block is a matter of convenience since other aspects of user management, like managing a user's identity, roles, and authorization also exist in this block. Figure 7.15 illustrates the primary classes that provide profile management features in the Security Application Block.

Figure 7.15. Profile Management in the Security Application Block


The ProfileProviderFactory Class and the IProfileProvider Interface

The ProfileProviderFactory class uses its configuration information to create an instance of a class that implements the IProfileProvider interface. The ProfileFactory provides public static methods that wrap around the ProfileProviderFactory methods. Either class can be used to obtain a named instance of an IProfileProvider or the one that is configured as the default.

The GetProfileProvider method is exposed by both of these classes and is overloaded two times: one overload expects a string that represents the name of a ProfileProvider that has been configured for the application, and the other overload does not expect any arguments. The first overload returns the named ProfileProvider, and the second returns the ProfileProvider that has been configured as the default ProfileProvider for the application.

There are two methods that a class must implement to support the IProfileProvider interface: GetProfile and SetProfile. The GetProfile method accepts an object that implements IIdentity and returns an object that represents the profile. SetProfile takes both an IIdentity object and an object that represents a profile and sets the profile for this Identity.

Note that the profile is just represented as an object for these methods; that is, there is no specific Profile object that needs to be passed into or returned from these methods. This gives you complete freedom to create your own representation of a profile. As long as your Profile object is serializable, it should be able to be stored by the ProfileProvider. It is the ProfileProvider's responsibility to serialize the object for the SetProfile method and deserialize it for GetProfile.

The DbProfileProvider

And that is exactly what the DbProfileProvider does. Enterprise Library ships with a ProfileProvider that stores and fetches the Profile information from the Security Database described earlier in this chapter. The DbProfileProvider gets the name of the DatabaseInstance from the configuration data that is stored for the Security Application Block. When SetProfile is called, the DbProfileProvider uses a BinaryFormatter to serialize the object and then calls two stored proceduresone to delete the profile if it already exists and another to insert the profile that was just serialized. Both are executed in the scope of a transaction. When GetProfile is called, the opposite occurs: the GetProfile stored procedure is called and the profile is deserialized and returned to the caller.

Creating a Custom ProfileProvider

The DbProfileProvider is very effective for storing profile information in the Security Database. Because it is indifferent to the specific type of object that is being stored, it serializes the Profile information and stores it in the database as binary. Therefore, the only requirement for using it is to ensure that the object that represents the Profile information is serializable.

There are, however, a few consequences associated with storing the Profile information as a single binary field.

  • The actual Profile information cannot be queried or updated without an application that will deserialize the Profile information first. For example, if the Profile information contains data that represents a user's favorite Web links, there is no way to simply run a database query to determine how many users have specific links (e.g., How many users have a link to www.msn.com?). To perform this type of query, code needs to be written that deserializes the profile data.

  • Additionally, there is no way to update specific fields in a profile. To update profile information, the DbProfileProvider actually removes the entire record that stores profile information for a user and adds a new record to the database. It performs this activity in the scope of a DbTransaction to ensure that either both actions succeed or that neither does. This could be less than optimal if there is a lot of profile information and only one bit of information needs to be updated.

I am not implying that the DbProfileProvider is ineffective. I think it is a very good provider and serves its purpose very well. There aren't many other ways to create a provider so that it doesn't require knowledge about the type of data that it is storing.

However, in your enterprise applications, you may know what type of profile information needs to be stored. There may not be the same requirement for the ProfileProvider to be so generic with respect to the type of data. Additionally, you may need to be able to query, update, and run reports against some of this profile information. If this requirement exists, then a custom ProfileProvider may be your answer. You can create a Pro fileProvider that knows how to store the specific data that makes up the profile. The following steps show how I created just such a provider named PortalProfileProvider, for an enterprise portal site.

Creating the Data Access Logic Component

First, I needed to represent the data that I want to use to represent the profiles for users of the enterprise portal. Because the data will not be stored as binary in the database but will be broken down into its pertinent fields, it is not necessary to make the representation of the profile serializable. Furthermore, since the data will be stored as separate fields and not one big chunk, there is no size constraint for the amount of data the profile can contain. Therefore, I can pretty much use whatever mechanism makes sense to me to represent the profile information; there are very few constraints.

I've represented the PortalProfile information as a typed DataSet. For this example, the profile will contain information about the different URLs in which a user is interested. It's a pretty simple representation, but it does not need to be. Your profile can be much more complex if need be. Figure 7.16 shows the typed DataSet as it appears in the Visual Studio XSD Editor.

Figure 7.16. A PortalProfile Typed DataSet


I've also created a table in the Portal database to contain this information and related it to the Users data table. The table is normalized so that there is a field for every item shown in the DataSet in Figure 7.16. I then created two stored procedures: one to insert and update records in the table and the other to retrieve the profile records given a user name.

Before I start the task of creating the ProfileProvider, I need to determine how I am going to retrieve the data from the database. I could have used the Data Access Application Block and hard-coded the ProfileProvider to map the fields in the DataSet to the parameters of the stored procedures. However, I created a Data Mapping Application Block (described in Chapter 9 and Appendix A.) specifically to avoid hard-coding this type of "configuration data." Therefore, I have designed the PortalProfileProvider to leverage the Data Mapping Application Block instead of directly using the Data Access Application Block. If you're not a fan of the Data Mapping Application Block, you can use the Data Access Application Block instead and code the mapping of SourceColumns to stored procedure parameter names in the provider. Listing 7.9 shows the code for creating the PortalProfileMapper.

Listing 7.9. PortalProfileMapper

[C#] public class PortalProfileMapper : DataMapper {     protected override System.Data.DataSet DataSetType()     {          return new PortalProfile();     }     public PortalProfile GetProfile(string userName)     {          return (PortalProfile)base.GetObject(userName);     }     public void SetProfile(string userName, PortalProfile profile)     {          ListDictionary explicitParams = new ListDictionary();          explicitParams.Add("@userName",userName);          this.set_ExplicitParameters               (profile.PortalProfiles.TableName,explicitParams);          base.PutObject(profile);     } } [Visual Basic] Public Class PortalProfileMapper : Inherits DataMapper     Protected Overrides Function DataSetType() As System.Data.DataSet          Return New PortalProfile()     End Function     Public Function GetProfile(ByVal userName As String) _                               As PortalProfile          Return CType(MyBase.GetObject(userName), PortalProfile)     End Function     Public Sub SetProfile(ByVal userName As String, _                               ByVal profile As PortalProfile)          Dim explicitParams As ListDictionary = New ListDictionary()          explicitParams.Add("@userName",userName)          Me.set_ExplicitParameters _               (profile.PortalProfiles.TableName,explicitParams)          MyBase.PutObject(profile)     End Sub End Class

Creating the PortalProfileProvider

Now that I have determined how to represent the Profile information and have confirmed that I can create, read, and update the information from the database, it is time to create the PortalProfileProvider. The PortalProfileProvider will use only the PortalProfileMapper.

Just like the other providers that have been created in this book, the first step is to create the runtime data class that will hold the configuration data needed. In this case, I just need to know which DataSetMapping will be used to map the DataFields to the stored procedure parameters. Therefore, the PortalProfileProviderData class contains a string that represents this DataMapper.

Once the data class has been created, the actual ProfileProvider can be created. The GetProfile and SetProfile methods will leverage the PortalProfileMapper to send and retrieve profile information to and from the database. Listing 7.10 shows the SetProfile and GetProfile methods. The full source code is on the book's Web site.

Listing 7.10. PortalProfileProvider's SetProfile and GetProfile Methods

[C#] public void SetProfile(IIdentity identity, object profile) {     ArgumentValidation.CheckForNullReference(identity, "identity");     ArgumentValidation.CheckForNullReference(profile, "profile");     ArgumentValidation.CheckExpectedType          (profile,typeof(PortalProfile));     PortalProfile thisProfile = profile as PortalProfile;     portalProfileMapper.SetProfile(identity.Name, thisProfile);     SecurityProfileSaveEvent.Fire(identity.Name); } public object GetProfile(IIdentity identity) {     ArgumentValidation.CheckForNullReference(identity, "identity");     PortalProfile dsPortalProfile =          portalProfileMapper.GetProfile(identity.Name);     if (dsPortalProfile != null)          ArgumentValidation.CheckExpectedType          (dsPortalProfile,typeof(PortalProfile));     PortalProfile profile = dsPortalProfile as PortalProfile;     SecurityProfileLoadEvent.Fire(identity.Name);     return profile; } [Visual Basic] Public Sub SetProfile(ByVal identity As IIdentity, _                           ByVal profile As Object)     ArgumentValidation.CheckForNullReference(identity, "identity")     ArgumentValidation.CheckForNullReference(profile, "profile")     ArgumentValidation.CheckExpectedType(profile, _               GetType(PortalProfile))     Dim thisProfile As PortalProfile =  _          IIf(TypeOf profile Is PortalProfile, _          CType(profile, PortalProfile), _          CType(Nothing, PortalProfile))     portalProfileMapper.SetProfile(identity.Name, thisProfile)     SecurityProfileSaveEvent.Fire(identity.Name) End Sub Public Function GetProfile(ByVal identity As IIdentity) As Object     ArgumentValidation.CheckForNullReference(identity, "identity")     Dim dsPortalProfile As PortalProfile = _          portalProfileMapper.GetProfile(identity.Name)     If Not dsPortalProfile Is Nothing Then          ArgumentValidation.CheckExpectedType _          (dsPortalProfile,GetType(PortalProfile))     End If     Dim profile As PortalProfile = _          IIf(TypeOf dsPortalProfile Is PortalProfile, _          CType(dsPortalProfile, PortalProfile), _          CType(Nothing, PortalProfile))     SecurityProfileLoadEvent.Fire(identity.Name)     Return profile End Function

That's it for the runtime part of the provider. The provider can now be leveraged to store the data for a profile into the specific database fields that have been created to represent it. At this point, the configuration information in the Security Application Block would need to be manually modified to leverage this provider. The better alternative, however, is to configure this provider with the Enterprise Library Configuration Tool. Therefore, I derived the PortalProfileProviderNode from the ProfileProviderNode and added a property for the DataMapper. This property was created in such a way that it provides a list of the available DataMappers that exist in the application's configuration. For more details on how to accomplish this, read Chapter 2 . The full source code for the PortalProfileProvider is on this book's Web site. Figure 7.17 shows how to configure the PortalProfileProvider by pointing it at the DataMapper shown in Listing 7.9.

Figure 7.17. Configuring the PortalProfileProvider





Fenster Effective Use of Microsoft Enterprise Library(c) Building Blocks for Creating Enterprise Applications and Services 2006
Effective Use of Microsoft Enterprise Library: Building Blocks for Creating Enterprise Applications and Services
ISBN: 0321334213
EAN: 2147483647
Year: 2004
Pages: 103
Authors: Len Fenster

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net