Share via


Developing Applications Using Windows Authorization Manager

 

David Crawford, Dave McPherson
Contributors: Durga Prasad Sayana, Mei Wilson, Shawn Wu, Sudheer Mamidpaka, Sunil Gottumukala, Sunil Kadam, Chris Jackson, Eric Huebner
Microsoft Corporation

August 2006

Information in this document, including URL and other Internet references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document. Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

© 2006 Microsoft Corporation. All rights reserved.

Active Directory, Jscript, Microsoft, MSDN, Visual Basic, Visual C#, Windows, Windows NT, Windows Server and Windows Vista , are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.

The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

[This is preliminary documentation and is subject to change.]

Contents

Introduction to Developing Authorization Manager Solutions
   Executive Summary
   Application Authorization Challenges
   Windows Authorization Manager
Application Security Design
   Application Authentication Model
   Determining an Application Authentication Model
Designing an Authorization Manager Solution
   Identifying Resources and Operations
   Determine Scoping Model
   Determining Management Model
Implementing an Authorization Manager Solution
   Store and Application Initialization
   Client Context Initialization
   Access Validation
   Updating Policy
   Performance
   Environment-Specific Design Considerations
Deployment Considerations for Developers
   Authorization Manager Availability
   Deployment Approaches
   Application Installation
   Active Directory Storage
   Using ADAM as a Store for Authorization Manager Policy
   Authorization Manager Transactional and Concurrence Situation
   Delegation of Administration
   Troubleshooting the ASP.NET Authorization Manger Store Provider
   Troubleshooting ADAM Access with Active Directory
Conclusion
   Additional Resources

Introduction to Developing Authorization Manager Solutions

Executive Summary

Who Should Read This Paper

The intended audience for this paper includes architects, developers, technical decision makers, and consultants involved in application authorization design and implementation efforts.

Paper Overview

As corporate security and regulatory compliance requirements increase, the need and demand for a simple and common application authorization model grows. Today's application developers and enterprise administrators are aligning around Role-Based Access Control (RBAC) but are confronted by differing terminology and implementations. Authorization Manager provides a consistent and flexibility RBAC framework for Windows-based applications.

Authorization Manager is a role-based application framework which provides runtime access validation methods, storage, and a UI to manage access control. Authorization Manager is available on Windows Server® 2003, Windows XP, and Windows 2000 (runtime only.) Authorization Manager is an alternative to custom authorization solutions that tend to be limited in features, poorly integrated with the system, or very expensive to design and maintain.

Authorization Manager provides a high-end authorization solution for .NET applications and COM applications. Authorization Manager supports the use of Windows integrated, Active Directory® Application Mode (ADAM) authentication, Active Directory Federation Services (ADFS) claims aware applications, and SQL Server or custom authentication. The Authorization Manager runtime is separate from the authorization policy store, which may be stored in Active Directory, ADAM, or XML.

This paper assumes understanding of basic Authorization Manager concepts. For a conceptual overview of Authorization Manager and information on Authorization Manager administration and operations see: Role-Based Access Control for Multi-tier Applications Using Authorization Manager.

Authorization Manager benefits include:

  • Intuitive Role Based Access Control (RBAC) Administration   A simple common role-based administrative experience; administrators learn fewer authorization models and require less training.
  • Natural Development Model   Easy to integrate with native or managed applications; provides broad RBAC management and enforcement functionality.
  • Flexible Authorization Rules   Ability to define membership through dynamic Lightweight Directory Access Protocol (LDAP) queries or custom BizRules.
  • Centralized Administration   Multiple applications can be managed centrally and leverage common application groups.
  • Flexible Storage Options   Ability to store policy in Active Directory, Active Directory Application Mode (ADAM), and XML Files or SQL™ Server (on Windows Vista™ Beta 2.)
  • Platform Integration and Alignment   Support for platform features such as Active Directory groups, Windows security auditing, and Microsoft Management Console ™ (MMC). Assurance of proper integration of system access control objects such as the Windows access token and better alignment for future Windows access control features such as provisioning and entitlement engines.
  • Reduced Software Development and Maintenance Costs   Developers avoid the expense or trade-offs of custom access control. Authorization Manager does the expensive work of a full-featured authorization solution, including a complete RBAC model, policy storage (Active Directory, SQL Server, or XML), an MMC user interface, built-in application group support, rule and query support, integrated system auditing, and performance optimizations such as caching and late-binding.
  • Enhanced Security   Platform technologies are rigorously tested, broadly used and continually refined. A common RBAC model leverages administrators existing knowledge resulting in fewer access control mistakes.

Application Authorization Challenges

Creating custom authorization components can be costly, lead to a less secure solution due to bugs, and get little refinement due to tight budgets and timelines. Many custom developed authorization models go through a metamorphosis as the features grow. Each developer who works with such an application has the opportunity to change how authorization is performed. In some cases, each new developer or development team produces completely different models for the same application. This leads to inconsistent application management. Even using the ASP.NET IsInRole() capability can lead to unwieldy code with branch logic and multiple approaches coexisting in the same implementation such as demanding permission.

Additionally, in too many cases, administrators have little input on the administrative capabilities and the authorization management interface provided. They also have to learn how to use several models with varying results to an application. The lack of administrative consistency may lead to mistakes and greater attack surface for an application.

Authorization Manager provides a robust and common model that eases the burden on developers. It standardizes the task of authorization modelling and authorization checks within code and thus provides a common administrative model.

Windows Authorization Manager

Windows Server 2003 introduced Windows Authorization Manager while Windows 2003 Service Pack 1 extends its capabilities. Downloads are available for other Microsoft operating systems such as the back-ported Windows Authorization Manager Runtime for the Windows 2000 platform. Authorization Manager is available for Windows XP from the downloadable Windows 2003 Service Pack 1 Admin Pack. (For information about obtaining Authorization Manager, see Deployment Considerations for Developers.)

Using a role-based authorization mechanism is an attractive option to the basic authorization functionality of .NET for the enterprise developer because it offers the following:

  • An intuitive RBAC model that allows configuration of role members and capabilities without requiring changes to applications.
  • Centralized authorization policy for multiple applications.
  • Security groups that you can create outside of Active Directory that an application administrator can manage.
  • Dynamic group membership based on the result of a Lightweight Directory Access Protocol (LDAP) query.

Windows Authorization Manager utilizes a centralized policy store that holds authorization policy for one or more applications. Each application's policy is described as relations between role, group, task, and operations definitions.

Management of application policy can be delegated to application administrators as long as you choose Active Directory as the policy store location.

Windows Authorization Manager may establish role membership from the following:

  • Active Directory or Local machine (SAM) accounts.
  • Windows Authorization Manager Application groups.
    • May contain Active Directory groups and other members.
    • Deny Membership may also be based on group membership.
    • Lightweight Directory Access Protocol (LDAP) queries on Active Directory or ADAM.
  • Active Directory Application Mode (ADAM).
  • Active Directory Federation Services (ADFS) claims.

An Authorization Manager LDAP Query Group is an application group that performs a runtime look up on a user's Active Directory or ADAM account object; the user is in the group if the LDAP query about the user, such as (title='auditor'), returns true.

Group membership and LDAP attributes are pieces of information that are often managed across the organization by identity management processes and technology. Application authorization through Authorization Manager may take advantage of this investment by conforming to the organizations security pattern reflected in the authorization policy store.

Application Security Design

Before integrating Authorization Manager, the application architect must complete the initial application security design and make architectural decisions such as the authentication framework selection. These decisions can be made based on application identity and security requirements. They are usually made independently of Authorization Manager considerations. This section provides information that will help you in this part of your design. After this security architecture is in place, you determine the application authorization model. Authorization Manager is designed to be flexible and will likely be able to be integrated with almost any authentication style you select.

Application Authentication Model

Application authentication options are typically driven by the type of user accounts being used. The user store selection (Active Directory, ADAM, ADFS, SQL Server, and so on) usually depends on business or organization requirements. Authorization Manager may be used for authorization with almost any authentication store, although non-Windows integrated authentication requires integration code for custom principals (described later in this paper), and administration using the Authorization Manager Microsoft Management Console (MMC) snap-in requires Windows identities. Administration for non-Windows identities is possible by writing a custom user interface using the Authorization Manager application programming interface (API). Developers often create these custom user interfaces integrated with the application management tools.

Authorization Manager can work with Windows integrated authentication types (in other words, those that result in a Windows logon token) at runtime, or ADAM authentication that yields Security Identifiers (SIDs), ADFS which issues claims, or even custom authentication types such as a SQL Server that could work similarly. The type of authentication chosen can imply features supported and integration requirements. For more information about choosing the appropriate authentication model, see the Microsoft patterns & practices site at https://msdn.microsoft.com/practices/ and the Security Guidance Center for Developers at https://msdn.microsoft.com/security/.

Determining an Application Authorization Model

Most applications that use Authorization Manager will use the trusted subsystem application model (also called the "protected subsystem" model) described below. Note that, while the two models imply distinct application designs, a particular application may choose to use a hybrid model that contains a trusted subsystem middle-tier design and use impersonation (with delegation) to maintain back-end auditing or to interface with legacy back-end resource managers.

Impersonation Model

Windows 2000 and earlier versions of Windows NT Server supported the impersonation model. In this model, server applications obtain a token for the connected client and impersonate the client token before attempting a secure operation, such as opening a file. The ACL on the file is compared to the user's token groups to determine whether or not that user has permission to open the file. The Windows Server 2003 family enhances support for the impersonation model through the Kerberos extensions of protocol transition and constrained delegation. Further information is available at Kerberos Protocol Transition and Constrained Delegation.

Figure 1: Impersonation model and trusted subsystem model

Trusted Subsystem Application Model

Authorization Manager adds support for a trusted subsystem application model to the Windows security infrastructure. In the trusted subsystem model, the application server account only has enough access so that it can perform all the operations it exposes to clients. When a client requests an operation, the middle-tier application server authorizes the client request based on authorization policy that is specific to the application. If the client has permission to perform the requested operation, the server performs the operation on behalf of the client. In this model, clients do not have direct access to resources. While the impersonation model has its strengths and is appropriate in many cases, the trusted subsystem model has some advantages over the impersonation model.

Management of Permissions

In the impersonation model, each client's account is used to access the back-end resource, so the Discretionary Access Control List (DACL) of all resources must be maintained by granting each user the appropriate level of access. When the number of resources that are secured separately grows, especially when stored on separate back-end computers, management of the DACLs becomes more tedious. For example, you might organize resources into units that are secured in the same way, such as protecting all files in a sub-tree the same way or organizing users into groups with similar permissions.

In the trusted subsystem model, only the middle-tier service account is used to access the back-end resources; therefore, the DACLs can be simplified by granting only the service account sufficient access to perform all required operations on the objects. A service account should not be given full control rights or be a local administrator, but should have just enough permission to perform the applications complete set of operations. Application designers can document the level of permissions the application needs on back-end resources. This reduces the management of DACLs, because only the service account requires access to them. The service is given permissions to the resources, so you must trust that the service will not accidentally let one user access resources of another.

Rights Abstraction

With the impersonation model, assigning DACLs to resources to grant users the ability to perform high-level tasks can be difficult. High-level tasks such as Submit Expense or Query Inventory may require several operations on back-end resources. Rights abstraction is the process of determining precisely which low-level resource permissions are required for high-level application operations. It can be time consuming, particularly when many secured resource types are used.

In practice, administrators sometimes grant excessive access to resources to save time. A trusted subsystem application model allows the administrator to grant access only to the service account. Since the access to the resources is broadly given to the service account, determining the correct access is easier: usually the server gives just enough access to perform all operations that the server application may need (as defined by the application designer). The administrator does not have to define and maintain different levels of permissions on DACLs. The application developer defines the application operations in terms of the application tasks so the administrator does not have to translate the application tasks to permissions on back-end resources.

Connection Pooling

You often can achieve better scalability by connection pooling. Since the impersonation model maintains the user context through the request to a resource server, a separate connection to the resource server is usually required for each user request. If you create a connection for every client, you prevent reuse of a single connection to a resource server, such as a SQL Server database. Likewise, when using LDAP, multiple queries are possible on the same connection; therefore, maintaining a single open connection is often preferred for scalability. A trusted subsystem server application authorizes the client's request and accesses the remote resource for the client in the context of the applications service account. Since a new connection is not required, the application server can use the same connection with resource servers to service requests from different clients.

Controlled Access Points

Some applications enforce rules and workflow in the application server. In the impersonation model, each client is given appropriate access to each resource. Since the client has access directly to the resource, they could potentially access the resource though a method other than through the intended applications, which could cause undesired access and manipulation of resources.

In the trusted subsystem model, only the application service account has access to resources. This means that users cannot directly gain access to resources through a tool or API. In this model, the application server has more control over how the user sees and manipulates the resources.

Auditing

Auditing allows administrators to examine which users have attempted to gain access to particular resources. Auditing is most authoritative if the audits are generated by the same application that accesses the resources. The impersonation model does this by maintaining the user's context when it requests access on each system. This allows the remote system to authoritatively log the user and the requested access.

When you use a trusted subsystem application model, the back-end resource managers generate audits that log the server's service account as the account requesting access, not the client user on whose behalf the operation was performed. Authorization Manager provides runtime auditing when the access validation is performed (in other words, when the AccessCheck API is called), but these audits are generated at the application server. Because of this, mapping an access audit on a back-end resource to the user who made the request requires that the audit that logs on the application server be correlated with back-end audits. The application server audits can often be more informative than audits made on the back-end resource. From the application server, you can know more about the high-level task that the client is requesting, so a greater degree of the users intent can be recorded in the audits. On the back-end resource managers, only low-level operation requests can be audited, so it may be difficult to determine the user's higher-level activities.

Other Differences

In the impersonation model, the service account in which the application server runs may have little or no access to resources. To access resources on behalf of the client, the application server must impersonate the client's security context, which the application server can obtain when the client connects to it. When you limit the permissions of the server context, the server is limited to the permissions of the connected client when a successful attack takes place that compromises the server. If attackers want to gain broad access to back-end data, they must compromise the server and wait for users to connect. As each user connects to a compromised server, the access of the attacker grows in proportion to the connected users' permissions. If a highly-privileged account logs on to the server, such as the applications administrator, the attacker quickly gains permissions to the application data. If client users who have access to resources other than the applications log on to the server, the attacker also gains access to those resources because of the ability to impersonate the client to those resources.

A trusted subsystem server application starts out with a high degree of access to application resources so that, if the server application is compromised, the attacker has broad access to these resources. But since the client is not impersonated, the attacker is limited to the access of the service application's security context (which can be limited to the application's resources). While the attacker gains broad access to application resources, the attacker cannot access non-application resources.

Another concept to consider is that those organizations that attempt fine-grained security by increasing the number of groups in their directory and then checking those groups as role names within their applications may find broader implications to this practice. As the permissions within an application increase so does the number of groups that correspond to those permissions that get loaded as a SID for each group in the user token. As the number of groups increase so does the time to logon and to check that list against objects with DACLs. The impact of this "token bloat" is typically more noticeable with large organizations when there are a large number of users accessing a single system. This isn't to say that organizations shouldn't use groups. In fact, groups provide enormous benefits within organizations; however, there is an optimal use for groups and, in the case of the trusted subsystem model, Authorization Manager provides an ideal store for the fine-grain permissions while striking a balance with the common runtime environment.

Designing an Authorization Manager Solution

Identifying Resources and Operations

The first step in integrating Authorization Manager into an application is to identify the application operations that need to be authorized. An operation is a low-level permission that represents privileged actions or capabilities of an application. Operations should be created for each routine, query, method, and so on that comprises a discrete application function which requires access control. An operation alone may not be enough to perform a high-level task and may be required for more than one high-level task, but the operation itself is always executed as a unit and must be secured.

For example, an operation identified as ReadOrderInfo alone may not be enough for the high-level task Process Order, and the same routines may be needed for another high-level task, such as Query Order Status. Creating an operation for the set of routines that are required to perform each order activity allows finer permission specification. The more precisely the operations are defined, the more flexibility you have when managing permissions. However, operations that are defined too precisely make administration more complex.

For many resource managers, operations correspond to securable routines, procedures, or queries that operate on data or resources. The operations may be low-level and meaningful only to the application developer. To create permissions that are meaningful to administrators, the application developer can group low-level operations into Authorization Manager task objects. The graphic below depicts roles, — Employee, Manager and Administrator — with the arrows indicating the task groupings. The operations are grouped into the task to form the fine-grain permissions in the application.

Figure 2. Operations and tasks

The following section will show where operations are viewed in the MMC, how they are created or defined (integers), how to create them programmatically in the store, and how they are used at runtime. Each Authorization Manager artifact will build from this low level concept.

Operations in the Policy Store

Figure 3. MMC Interface Map to Code for Operations

The above operation could be written to the authorization store using the MMC UI or the following code snippet. The operation number is used at runtime in the code and the name is there to provide readability for the application developers who would group these into tasks for application administrators. Operations may be created by right-clicking the folder and adding the new operation through the context menu.

Programmatically Add Operations in the Policy Store

The following code will programmatically add an operation same as the above diagram into an Authorization Manager policy store. The operation ID is 3, which matches the MMC user interface Operation Number. This is useful for automating the creation of the policy store or application within. Policy store management tasks may all be performed programmatically and a more complete example may be found in the managed and script sample code at the end of this paper.

AzManOp = AzManApp.CreateOperation("ReadClaim")
        AzManOp.OperationID = 3
        AzManOp.Submit()

The code below shows an access check (performed at runtime), which compares the client's context and determines ability to perform an operation. The result array's first index in this case would contain a true (S_OK = 0) or false (anything not equal to 0) value based on their role assignment. Most wrappers today transform the COM result to a managed Boolean type.

object[] result = (object[]) clientCtx.AccessCheck(
auditIdentifier,              // The name used in auditing
internalScopes,               // Scopes
operationIds,                 //operationIds[0] = myopid = 3
null, null, null, null, null  // used for BizRules and Role filtering
);  

Authorization Manager is flexible enough to be used in a similar manner to COM+ roles. A developer could write a wrapper and control access to a given method call handling security error messages or performing per item access control. This could be done by passing an operation ID for every field on an ASP.NET page into a single access check and using the Boolean array return values to set the visibility property of a set of user interface controls.

Determine Scoping Model

Using Scopes

Authorization Manager scope objects are collections of resources in which all resources of similar type have the same permissions. They can be simple collections such as "C:\My Documents", or more dynamic collections such as "*.doc", but for consistency applications should always present scopes as collections of application resources. Since each application exposes differing types of resources and different management abstractions, the application must take into consideration what scoping logic it will use and how it will be communicated to administrators.

A scope provides flexibility to the application in how to apply different access control to different resources. Depending on what the application is controlling access to, the collections may vary in form. For example, if an application performs operations on other computers, then a scope may represent collections of computer objects and be named something like "Computers in Sales Dept" or "Computers Running Windows XP." The primary requirement is that the application must be able to map a user request for access to a resource to the scope containing the resource when the access validation is performed.

Since scopes are defined by each application, and the application will map each access request to the appropriate scope, the use of the scoping model can vary across applications. While the Authorization Manager engine allows much flexibility in how the application wishes to integrate the scope, administrators must be able to understand the resulting abstraction. Therefore applications should be consistent in using the scope as collection of application resources and be careful to present it as such when using a custom UI.

General Scope Guidelines

The authorization administration should be clear and simple to manage. Scopes should not be abstract containers of resources that an administrator would not understand if the application is not automating it. Generally, somewhat static commonly used attributes of a resource, such as name or location, are understandable to administrators. When rarely used or when highly dynamic resource attributes (such as "last modified" or "Size") are used to group resources it is more difficult for administrators to visualize the resources contained in that group.

Scopes are easiest to understand if they are used consistently through the application. For example, if an application uses the path or location of the resource as scopes then it should not also use unrelated attributes (such as creator) for scopes; doing so will confuse administrators about which resources are in which scope and which scope has precedence.

It is simplest for the administrator when applications manage the creation of scopes and the administrator then creates and/or manages the roles within a scope. This way, the administrator does not have to know the application's scope name syntax and the application logic that is used to associate user access request with a scope.

When applications do allow administrators to create scopes, the application must provide documentation so that the administrator understands how to create scopes that are named correctly, since scopes are defined by each application and the application will map each access request to the appropriate scope. Requiring the administrator to create scopes adds complexity to both the application design and administration. The application must be designed to anticipate the creation of a scope and use the scope as appropriate, while the administrator must be able to understand how the application maps resources to scopes. If inheritance or overlapping is supported between scopes, then the administrator must understand the order in which the application will apply the scopes in granting access. For example, if a resource can be mapped to multiple scopes then the administrator must understand if the application will OR or AND the permissions granted by each scope a given resource maps to. Since the mapping of the resource requested to a scope and access enforcement is done within the application, the application will choose if and how to determine scope precedence.

Ideally applications should work to minimize the number of scopes (hierarchical or not.) BizRules can sometimes be used to employ generalized rules that allow data that would otherwise be in separate scopes to be managed in the same scope. For example, instead of making a scope for each user's mail box granting each user typical owner permissions, a rule could take the requestor's name and compare that to an attribute for owner on the mailbox object. Thus you don't need a scope per mail box.

Scoping example 1: Resource full-name to scope mapping
Many resource managers expose a management model that orients the authorization management process around the location of the resource. Typically file systems and Web servers are examples of such resource managers. In these resource managers administrators must understand the physical layout of the resources. Managing the authorization policy on the resource is done by locating the resource or a container of the resource and assigning permissions.

Mapping these resource managers into a scope model is straightforward. A scope can be directly mapped to a resource (file or URL) or a resource container (directory or VDir). Since these resource managers are typically hierarchical it is often necessary to provide a means of inheriting policy from parent containers to children containers. It is necessary to identify rules to determine the applicability and priority of multiple policies when policy can be specified at the resource, the resource container, and any ancestor containers of the resource. A common method of providing this inheritance and priority is through a longest path match algorithm that attempts to locate policy for a requested resource by looking for a scope object that matches the complete name of the resource including its path.

For example, a file system may have the following roles defined (Tasks simplified):

  • Administrator: Can do anything to file.
  • Author: Can create, read, and write to a file.
  • Reviewer: Can read a file.

This may be in the following object hierarchy.

Figure 4. Hierarchical scopes

A scope is defined as a collection of resources in which each resource of similar type has the same authorization policy. In this case the following scopes are defined.

Scope Name Contents Administrator Editor Reviewer
A Folder A and any non-scoped contents Local Admins   Local users
B Contains the folder B and the file 1.doc Local Admins Bob Kim
2.xls S3 = 2.xls (contains the 2.xls file) Local Admins Bob Bob
*.doc All files of type doc Local Admins   Bob

Runtime scope mapping

At runtime a request is made for a specific file or directory (read, write, delete, list, and so on.). The file system must match the request to the correct scope. It can do this by first looking for a scope that exactly matches the full name of the resource. If a scope cannot be found, the algorithm looks for a longest prefix pattern match. Comparing the prefix of the requested file to the names of existing scopes (note that the algorithm can be smarter than this to support wild cards within a path such as C:\my docs\*.doc.) The algorithm continues to search for a scope until a scope is located or the root of the path is reached. If wildcards exist at any level, the algorithm attempts to match the wildcard at that level in the prefix search after checking for an exact match.

Scoping example 2: Resource attribute to scope mapping

Other resource managers hide the physical storage model from the administrators. Database applications, for example, commonly have schemas optimized for query performance and the physical layout of the data is not directly used for administration. These applications typically provide a management application which implements a unique management abstraction for the nature of the resources and the type of administrative tasks that are necessary.

An example of this non-location based scoping is an expense application in which an expense report is a resource. Each expense report is persisted as a row in a database. The management of expense reports is not done though a database UI but instead the application provides a UI that exposes management capabilities to users as appropriate based on their permissions. Actions can be performed on expense reports by the user who created them, the managers of that user, the auditors and reporters of the cost center to which the expense report is billed, and the administrators of the Expense Report application. In this application the management abstraction is to virtually collect expense reports based on their cost center attribute

The scoping may look like this:

Figure 5. Attribute-based scopes

The following roles are defined:

  • Submitter: Can submit and cancel an expense report.
  • Approver: Can approve an existing expense report.
  • Auditor: Can read existing expense reports.
  • Administrator: Can read, delete, and approve existing expense reports.

The following assignments are made in each scope:

Scope Name Submitter Approver Auditor Administrator
Cost Center 1000 Dynamic group defined as user's whose costCenter attribute in Active Directory equals 1000 Dynamic group that determines if the current user is in the chain of command of the person who submitted the report Username of employees whose job it is to audit expenses Usernames of Expense application administrators
Cost Center 1001 Dynamic group defined as user's whose costCenter attribute in Active Directory equals 1001 Dynamic group that determines if the current user is in the chain of command of the person who submitted the report Username of employees whose job it is to audit expenses Usernames of Expense application administrators
Cost Center 1002-2000 Dynamic group defined as user's whose costCenter attribute in Active Directory is between 1002 and 200 inclusive Dynamic group that determines if the current user is in the chain of command of the person who submitted the report Username of employees whose job it is to audit expenses Usernames of Expense application administrators

Runtime scope mapping

Access verification is done through an algorithm that checks the costCenter attribute of the expense report being operated on and looks for a scope assigned to that costCenter. The scope may contain multiple costCenters in a numeric range. In this case the algorithm first checks for a scope name matching the cost center exactly and then looks for a scope whose name specifies a range that includes that of the target cost center.

Scoping example 3: Dynamic resource attribute scope mapping

A third example of scope mapping is a hybrid of the resource mapping example. An application management abstraction that collects resources based on the attributes of the resource and the attributes of the current user. This is done to allow a common permission template to be applied to many different resources, while the actual authorization may be different to each resource depending on a dynamic variable such as the identity of the person attempting to operate on the resource.

For example, an application that manages mail boxes for users may have a default permission set that each user gets to his or her own mailbox. Instead of creating a separate scope for each user, which requires more management and more storage for each scope, the common template can be implemented in a single scope called self. The self scope would contain roles granting the default permissions an identity called self.

The application may allow a user to override the default permissions. This can be done by creating a new scope for the mailbox with the permissions specified by the user. To allow administrator to provide access to overridden scopes, a common "management override" scope is maintained which grants permissions to managers.

Runtime scope mapping

When a user requests a mail box, the access validation is done by checking for a scope matching the specified mailbox; if one exists then an access check is done against that scope. If not, the algorithm checks to see if the current user is the owner of the mailbox and, if so, it does an access check for the requested operations in the "Self" scope. If at this point the requested access is not granted then the algorithm checks to see if the current user is a manger of the owner of the specified mailbox and, if so, it does an access check against the "Management Override" scope.

Scoping example 4: Work on Behalf

This illustrates the flexibility the scope introduces. The scope object can also be used to solve the problem of allowing one user to delegate some of their permissions to another user. To extend the above cost center scenario: a manager who is going on vacation or infant care leave may wish to delegate some of their capabilities to an interim manager. Or a director may wish to grant a subset of permissions in the expense application to an assistant.

To implement this, we can maintain Work on Behalf (WoB) scopes beside the existing costCenter scopes; one WoB scope for each user who is delegating permissions to another user as shown in the following figure:

Figure 6. Work on behalf

To initiate the creation of such a Work on Behalf scope an e-mail is sent from the party who wishes to delegate permission to another party.

Within the scope the delegating party has the ability to define a custom role in terms of the tasks the delegating party is capable of and assign these roles to the users of their choice. The delegating party may create as many roles and assign as many users to them as desired.

Runtime scope mapping

For a user assigned to a WoB role to receive the access of the role at access validation time, they must specify whom they are working on behalf of. When access to an expense report is requested an access check is performed against the WoB scope of the user they specified, which validates the user has the specified access within the WoB scope. If the user has the requested access via the WoB scope, then another access check is performed against the applicable cost center scope (as done in the original scenario) to insure that the delegating user had access to give. To do this, a context for the delegating user is constructed (via something like InitializeClientContextFromName.)

Extensions to this scenario are to allow the application administrator to control the set of tasks (or roles) that a user may delegate to another user, and for the delegating user to specify a time limit on the membership of the delegate that will cause the delegate to be automatically removed from the roll on expiration.

Scoping Example 5: Query based scopes

Database applications that primarily expose data through queries (sometimes called "views") often control access to resources by permissions on the query objects instead of the tables or records in the database. In this case, views are created for each administrative and runtime task that the application exposes: for example, a view called "My Orders," which enumerates the order records whose owner attribute is that of a specified user, or a view for "NE division" orders, which enumerates all orders whose division attribute is set to North East.

Here access to the view implies access to each object returned in the view. This makes overlapping queries possible and easily managed. This also simplifies the programming model, which only needs to check access around the relatively small number of queries and not on each object. A drawback to this model is that if you want to know who has access to a particular object then you must execute each view (query), check for that object, and then enumerate the permissions for each view that includes the object. This will take more time than models where each object has a single policy specified; this will get worse as the number of objects and views grows. In some applications, checking the permission directly on a single object is common, so this model would be unacceptable. But in situations where the object's access policies are rarely examined per object, this model can simplify administration and development.

In such an application the following roles are defined:

  • Sales Rep: Can create, read, update and delete their own orders.
  • Sales Mgr: Can read update any order in there group.
  • Auditor: Can read any orders.
  • Administrator: Can read and delete existing orders.

The scopes are defined as follows:

  • Scope 1 = View 1 = all orders submitted by a specific sales rep
  • Scope 2 = View 2 = all orders in a specified group
  • Scope 3 = View 3 = all non filled orders.

Figure 7. Query-based scopes

Where each scope is assigned as follows:

Scope Sales Rep Sales Mgr Auditor Administrator
View 1

Enumerates orders entered by current user

All sales reps.

(Runtime View takes current user name)

All managers

(Runtime View takes current user name)

Username of employees whose job is to audit orders User names of order application administrators
View 2 Sales Reps specified group. Sales managers in specified groups. Username of employees whose job is to audit orders in group User names of order application administrators in group
View 3   Global managers Username of employees whose job is to audit orders globally User names of global order application administrators

Runtime Scope Mapping

At runtime the app controls the presentation and orders are displayed as results in selected views. The user will select the view that corresponds to the activity they're performing based on the name of the query. The app will compare that name to a scope name and do a check access to see what the user can do to orders returned by that view. If the user has read access to the view, the app executes the view and lists the results. As the user attempts to update or delete an order, the app verifies the permission against the result from the original access check to see if update or delete is granted, the original access check can be used if the application enforces that the resource was within the view either explicitly by context. In this situation all permissions are managed at the view and not at the resource (the order record.) If the scope (view) of a particular resource is not implied or known by context, the access check on the resource must execute each view where each view that includes the resource identifies a scope. The application would decide if the requested permissions are needed in one of the set of scopes or in all (AND vs. OR.). Since executing all views (queries) at access check time will decrease performance, the application may optimize by making the set of views to which a resource applies an attribute of the resource. This would be the set of scopes containing the resource. This would require that when an object is created each view is executed to determine which apply to the new object. Each time an object is modified, views involving the modified attributes would be executed to determine if they apply and the object's views attribute would be updated. Likewise, when a view is created or modified, it would have to be executed to determine which objects it applies to. At administration time such filters could be more acceptable at runtime.

Determining Management Model

Defining tasks

The application should define operations for each routine that will run as a single unit and must be secured. While this allows the application to grant access more precisely, multiple operations may be required to perform a high-level task that the application exposes to the user. In this case, you should determine which operations are required to perform the high-level task and create an Authorization Manager task object to conceptually unify them as a task so administrators can treat them as a single permission. This requires some research on the part of the designer. Task objects are designed to simplify role management by providing meaningful, high-level permission sets that correspond to the application tasks that an administrator would grant a user permission to perform. Therefore, it is important for applications to install with a complete set of tasks required to control access for all uses of the application. The Role Definition and Modeling section, following, illustrates the use of task objects to combine low level operation permissions into high-level tasks. 

Role Definition and Modeling

The Authorization Manager conceptual model prefers that a role is defined as a set of permissions at the application level (a role definition) and assigned as needed per scope (a role assignment). This attribute of the conceptual model prevents role bloat — a situation where different roles are created for each tuple of principal, permission, scope — while still providing for roles to be managed at the application level. Self-defined role assignments (what we've called role assignments that directly link to operations or tasks without a role definition while in scopes) makes application views harder. Therefore, it's recommended that each role be defined at the application layer.

The following describes a high-level authorization design for an application using Authorization Manager in an effort to bring some of the concepts together. Many applications run with classic Create, Read, Update, Delete also known as (C,R,U,D) operations.

Operations: Create, Read, Update, and Delete

Figure 8. Operations view

The diagram above is the Microsoft Management Console (MMC) or AzMan.MSC view of managing policy stores. In particular, there are three XML stores shown; however, we are looking only at the AzStore.xml policy store that contains two applications, Expense and Contoso Insurance. We expand Contoso insurance while the MMC is in developer mode, and then expand the definitions node and select Operations Definitions where we currently view the four operations in the right panel.

Task: "Maintain Policy"

Contains Operations: Create, Read, Update, and Delete

Figure 9. Maintain Policy task operations

In this case, we assign all operations to the Maintain Policy task.

Task: "View Policy"

Contains Operation(s): Read

Figure 10. View Policy task operations

The View Policy task will only have the Read operation assigned to it.

Role Definitions:

Insurance Agent, Contains Task: Maintain Policy

Insurance Auditor, Contains Task: View Policy

Figure 11. Role Definitions

This view shows the Role Definitions that contain the tasks previous assigned. These operations so far have been focused completely on the authorization structure or what the roles may do as far as the application. The next step is assigning users and groups to these roles. This is the point where there is opportunity to leverage the existing investment in the identity infrastructure.

Scope: Washington Policies

Roles assigned:
Agent: (assign to Washington agents)
Auditor: (assign to Washington auditors)

Scope: Oregon Policies

Roles assigned:
Agent: (assign to Oregon agents)
Auditor: (assign to Oregon auditors)

Figure 12. Role Assignments

Once the user or group is assigned to a role, at runtime the application will pass the operation and scope to an AccessCheck function, which determines if the current user is assigned to a role that has permission to perform a request operation. If so, AccessCheck will indicate success; if AccessCheck cannot find an assigned role with the requested access, it will return failure and the application will then deny access accordingly.

There is another capability available for when a date/time or calling a function from a legacy application is required. Authorization Manager offers extensibility by calling custom code or runtime business rules. These custom code rules may be attached to role and task definitions. The following explains their use in greater detail.

BizRules

The Authorization Manager task object has the additional ability to dynamically qualify the permissions granted in a task, based on the result of an attached VBScript or JScript associated with the task. This allows for the access control decision to take into account runtime data, such as the dollar amount of an expense that has been requested, the inventory of a requested item, or the license status of the insurance agent attempting to view the insurance policy. BizRules are run during the AccessCheck call, and will run in the context of the Web application in the thread calling AccessCheck. If the BizRule returns success, the user receives the requested operations that were associated with the task. If the BizRule fails, the client is not allowed to perform the operations that are associated with the task. The user may be able to perform those operations through a separate task. Parameters are sent to the BizRule through the AccessCheck call, which takes corresponding name and value arrays as its parameters. For example, the first element in the varParameterNames array is the name corresponding to the first element in the varParameterValues array. The BizRule parameter's names array (varParameterValues) must be ordered accordingly so that names and values have the same index.

An application's authorization policy can have many tasks. Each task may or may not have associated BizRules, and new tasks and BizRules may be added by application administrators. At design time, application developers do not know if a call to AccessCheck will need to specify BizRule parameters (since BizRules may not be used for any given call to AccessCheck), so the application must send in the parameters potentially needed by BizRules. To facilitate BizRule developers, applications should define and publish the set of BizRule parameters that are sent into each AccessCheck call.

The type of data that AccessCheck should send as BizRule parameters includes information such as user name and restriction data. Restriction data may include items such as amount limits, account or ID numbers, or a user's manager. Any data that may be useful in determining whether or not to grant access at runtime can be used in each AccessCheck call and could be included in the applications list of published BizRules parameters.

BizRules require some scripting knowledge, and therefore it is not appropriate for most administrators to create and modify them. Usually, BizRules should be developed and shipped with the application or provided by the application vendor or other developers later. Since each BizRule that is run invokes the Windows Script Engine, BizRules should be computationally small tasks, such as comparing given parameters or querying a database.

Sample BizRules

The first sample utilizes JavaScript and the second VBScript.

This BizRule written in JScript insures that the time of day is between 9:00 and 17:00, using a 24-hour clock:

AzBizRuleContext.BusinessRuleResult = false; 
dt = new Date(); hour = dt.getHours();
if (hour > 9 && hour < 17) 
{ 
  AzBizRuleContext.BusinessRuleResult = true;
} 

This second sample is a VBScript BizRule that makes sure an amount that is passed into the BizRule as a parameter named ExpAmount is less than 500:

Dim Amount AzBizRuleContext.BusinessRuleResult = FALSE Amount = 
AzBizRuleContext.GetParameter("ExpAmount") if Amount < 500 then 
AzBizRuleContext.BusinessRuleResult = TRUE 

When a BizRule refers to a parameter, AccessCheck caches the parameters, values, and result for a particular BizRule for the life of a client context. This allows improved performance in subsequent AccessCheck calls on the same client context that references the same BizRule with identical values. This means that, if BizRule results can change in subsequent AccessCheck calls without changing the BizRule parameters (as in the time-of-day BizRule described above), then the cached value may not be the correct value. For this reason, applications using BizRules should free the client context or call the IAzApplication::UpdateCache method periodically.

BizRules using .NET

Managed or .NET application developers may want to develop BizRule logic using managed code. The following shows how managed code within the application can be called from within a BizRule script. The first concept to understand is the COM callable wrapper.

Simulating COM Interfaces

The cpconCOMCallableWrapper" (CCW) exposes all public, COM-visible interfaces, data types, and return values to COM clients in a manner that is consistent with COM's enforcement of interface-based interaction. For a COM client, invoking methods on a .NET Framework object is identical to invoking methods on a COM object.

To create this seamless approach, the CCW manufactures traditional COM interfaces, such as IUnknown and IDispatch. As the following illustration shows, the CCW maintains a single reference on the .NET object that it wraps. Both the COM client and .NET object interact with each other through the proxy and stub construction of the CCW.

Figure 13. COM interfaces and the COM callable wrapper

In addition to exposing the interfaces that are explicitly implemented by a class in the managed environment, the .NET Framework supplies implementations of the COM interfaces listed in the following table on behalf of the object. A .NET class can override the default behavior by providing its own implementation of these interfaces. However, the runtime always provides the implementation for the IUnknown and IDispatch interfaces.

If the business rule exists as a .NET function in the process of the caller, the COM business rule will resolve to the function from the .NET code when executed. Make sure to add another class to your .NET application such as the following. (This works with .NET version 1.X but an explicit interface is required in .NET 2.0.)

Figure 14. BizRules

From the Authorization Manager MMC, select the task definition, right-click and select Properties, click the Definition tab, and then click Authorization Script above the Authorization Rule dialog box.  

Figure 15. Managed BizRules

The above Visual C# pseudo code represents a class that could be added to an existing managed application that would perform a lookup function and contain data used in the BizRule. In this simple case, we make our function call in managed code and set a seed or arbitrary value of 200 in the constructor for this case. The m_DotnetBizRuleClass object retrieves a value from the user interface of the application (Amount.Text). The vbscript ("Amount") loads the value and runtime and is compared with the BizRule amount set in the policy store (500) as seen above. The DotnetBizrule class name and object are stored as objects in the access check. The value specified in the business rule at runtime is executed using the object that is in process, and then returned as a Boolean. The following shows how to use a business rule with .NET using the COM Callable Wrapper to access a .NET class running in process with the Authorization Manager COM objects.

 try 
{
azStoreOpen.Initialize(0, PolFile.Text, null);
app = azStoreOpen.OpenApplication2("MyApp", null);
clientContext = app.InitializeClientContextFromToken(0, null);

DotNetBizRuleClass m_DotNetBizRuleClass = new DotNetBizRuleClass("200");
            m_DotNetBizRuleClass.setAmount(Amount.Text);
            object[] oScopes = new Object[1];
            oScopes[0] = null;
            object[] oOperations = new Object[1];
            oOperations[0] = 1;
            object[] oInterfaceName     = new Object[1];
            object[] oInterfaceFlags    = new Object[1];
            object[] oInterfaces        = new Object[1];
            oInterfaceName[0]   = "DotNetBizRuleClass";
            oInterfaceFlags[0]  = 0;
            oInterfaces[0] = m_DotNetBizRuleClass;
            object[] results = 
                                        (object[])clientContext.AccessCheck (
               "TestApp", 
               oScopes, 
               oOperations,
               null,
               null,
               oInterfaceName,
               oInterfaceFlags,
               oInterfaces);
            bool bAuthorized = true;
            foreach(int i in results)
            {
               if ( i != 0 )
               {
                  bAuthorized = false;
                  break;
               }
            }

      DebugMsgWithLine("Authorized: " + bAuthorized.ToString());

         }
         catch(Exception Excp) 
         {
         DebugMsgWithLine("Exception Caught: " + Excp.ToString());
         }
      }

Implementing an Authorization Manager Solution

Server applications use the Authorization Manager runtime interfaces to manage connections to the authorization policy store and validate client requests. Although there are many possible environments and requirements supporting those environments when integrating Authorization Manager functionality, each application will follow the same high-level or common steps to validate client access requests.

Runtime Authorization Steps

The following steps are required in an Authorization Manager application. Each step is expanded in the following section.

  • Store and Application Initialization   Connect to the Authorization Store and open your application's corresponding application object.
  • Client Context Initialization   A user makes a request to access a resource. This is the point where claims or ADAM SIDs specific to the application user may be added.
  • Access Validation   Check access to a given operation (or scope and operation) using the AccessCheck method.
  • Updating Policy   In order to get updated, authorization policy applications must periodically update the cached policy store.

Store and Application Initialization

When the application runs, it initializes the Authorization Manager policy store using the IAzAuthorizationStore2::Initialize interface. To initialize the application, the process identity must have read access to the authorization store, but may not require write access to the store. To grant the application read or write access, use either the Authorization Manager Snap-In UI or call the IAzAuthorizationStore::PolicyReader or IAzAuthorizationStore::PolicyAdministrator methods.

After connecting to the store, the application calls IAzApplication2::OpenApplication to initialize an interface to its specific application within the policy store.

Initializing a store and application loads policy from the store, which will take some time depending on the size of the store. Most applications should initialize the store and application once at application initialization and cache the store object and application object for the life to the application.

Initializing an Authorization Manager policy store looks like the following in C# managed code:

AzManStore = new AzAuthorizationStoreClass();

AzManStore.Initialize(0, PolicyURL); 

// 0 above is the default – modes are documented in MSDN
// Constants are available for the other modes e.g. 
// AZ_AZSTORE_FLAG_BATCH_UPDATE often used for install but not access checks
// AzManApp = AzManStore.OpenApplication ("Application Name",null);

Note   For the XML store, Authorization Manager reads in and caches all application policy when IAzAuthorizationStore::Initialize is called. The Active Directory and ADAM policy stores use a delay-load technique that allows initialization to proceed faster. Delay-load will load only store-level groups and application objects; it will defer loading objects within an application (operations, scopes, roles, tasks, and groups) information until the IAzAuthorizationStore::OpenApplicaion call is made. Similarly, IAzAuthorizationStore::OpenApplication will only load application level objects and scope objects. Child objects under each scope will not be loaded until a request is made (such as an AccessCheck call) for the scope's policy.

Client Context Initialization

When a client connects to the application, an Authorization Manager context must be created for the user.

To do this, use the following interfaces.

IAzApplication2::InitializeClientContext2 (new with Windows 2003 SP1), IAzApplication2::IntiializeClientContextFromToken,

IAzApplication2::IntiializeClientContextFromToken2 (for Windows 2000), IAzApplication2::AzInitializeClientContextFromStringSID, or the IAzApplication2::InitializeClientContextFromName method.  

For performance purposes, whenever possible you should use a token to create a client context. A token is usually the result of a Windows logon and will contain logon information, such as whether it was an interactive or a network logon. Client context creation from a token is usually faster because it does not have to query a domain controller for group information. In rare cases, it is possible that role assignment may have been given depending on these logon properties, so whenever possible use the FromToken form of InitializeClientContext2 APIs. Using the InitializeClientContextFromName (or SID) method, an IAzClientContext2 object is created by looking up the user's group assignments in Active Directory. Since the user has not logged on, the context will not contain groups such as the NT AUTHORITY\INTERACTIVE group that identifies the logon type. In this case, if membership in a group or role is given to the interactive group, a user context that is created using InitializeClientContextFromName does not receive the membership. It is recommended that you do not use these logon property groups to secure resources or specify memberships in groups or roles, in which case InitializeClientContextFromName and InitializeClientContextFromStringSID can be used.

If you do use InitializeClientContextFromName or InitializeClientContextFromStringSID, you can add SIDs to the resulting client context via the IAzClientContext2::AddSids method.

The AzInitializeClientContextFromStringSID method creates an Authorization Manager context from a given SID in textual form. This behaves in a similar manner as the InitializeClientContextFromName method. When the AZ_CLIENT_CONTEXT_SKIP_GROUP flag is used, the AzInitializeClientContextFromStringSID method does not attempt to determine the group memberships of the given SID. The resulting client context only contains the specified SID. If the IAzAccessCheck method is called from this client context instance, role membership is only granted if the specified SID is used as a member of a role or group assigned to a role.

The InitializeClientContext2 method creates an empty Authorization Manager context. The interfaces added with Windows 2003 Service Pack 1 provide the interfaces needed to load SIDs and roles to this context (see IAzClientContext2::AddSids, IAzCleintContext2::AddRoles, and IAzCleintContext2::AddGroups). The new IAzCleintContext2::LDAPQueryDN property allows the access check to look up group membership from ADAM. ADFS also requires an empty context and, when an ADFS object is loaded with claims, they can be loaded into the Authorization Manager context for authorization.

Caching the Client Context

Many applications will benefit from implementing a cache of client context objects. If the usage pattern of your application is such that clients typically make multiple requests within a short time, then it is likely that caching the client context in your application will improve performance.

Review of Client Context Initialization Approaches

Empty   InitializeClientContext2 is used for manual population scenarios such as ADFS and ADAM. When initializing an empty context the application will add the SIDs, roles, or groups to the context manually. The ADFS application typically populates roles and groups that were received from the ADFS claim transformation process, whereas with ADAM typically SIDs are populated.

Token   InitializeClientContextFromToken uses the Windows logon token. All SIDs are in the token already; therefore, this is the fastest and preferred way to initialize a client context because no population of group SIDs from the domain controller is required.

SID   InitializeClientContextFromSid is used when only the client SID is available. This is used in some tiered application environments as well as some store and forward scenarios. When using InitializeClientContextFromSid, pass the string SID of the Windows user, and Authorization Manager will perform the look-up on the domain controller and populate the SIDs to the context. Since these lookups are required, this mechanism is slower than InitializeClientContextFromToken.

Name   InitializeClientContextFromName is used when only the name of the client is available. This is used in some tiered application environments as well as some store and forward scenarios. You can pass in the username, domain\username, or UPN and Authorization Manager will populate SIDs and groups for you. Using the username alone (without a domain specified) can result in an incorrect client context if the same username is used in a trusted domain. Since these lookups are required, this mechanism is slower than InitializeClientContextFromToken.

The following is a typical managed code approach for initializing client context.

// Have AzMan use the process token
// IAzClientContext ctx = app.InitializeClientContextFromToken(null, null);
////////////////////////////////////////////////////////////////////////////////

// another approach to grab a token is from a WindowsIdentity or IIdentity Interface
WindowsIdentity wid = (WindowsIdentity)User.Identity;
IntPtr tokenhandle = wid.Token;
IAzClientContext ctx =
app.InitializeClientContextFromToken((ulong) tokenhandle, null);
///////////////////////////////////////////////////////////////////////////////// 
//By token again…
IntPtr tokenhandle=WindowsIdentity.GetCurrent().Token;
IAzClientContext ctx = 
     app.InitializeClientContextFromToken((ulong) tokenhandle,null);
// By Name…
IAzClientContext ctx= app.InitializeClientContextFromName(userName,dom,null);
// Retrieve a name via WSE (a token could be utilized using Kerberos)
// There are a few possibilities in the web services side.
       userName = MapSoapContextToUser(RequestSoapContext.Current); 
/////////////////////////////////////////////////////////////////////////////////// 
// Retrieve the token at the ASP.NET early in the ASP.NET Execution Chain
// On Authentication would be a good place…
HandleRef token = new HandleRef(
     new object(),
     ((HttpWorkerRequest)((IServiceProvider)    HttpContext.Current).GetService(
          typeof(HttpWorkerRequest))).GetUserToken());
// The following demonstrates and AZMAN_APP object store in the ASP.NET Application 
// space
AzManClientContext = 
     ((IAzApplication)Application["AZMAN_APP"]).InitializeClientContextFromToken(
          (UInt64)token.Handle, 0);
 
//
//          Save the client context in a session variable
//
Session["AZMAN_CLIENT"] = AzManClientContext;

////////////////////////////////////////////////////////////////////////////////////
// ADFS, ADAM :  EMPTY  
//  – Empty context building – Add Groups, Roles
// Typically Add Sids with ADAM 
_azApp = _azStore.OpenApplication2(_application, null);
_azContext = _azApp.InitializeClientContext2(_application, null);  

Access Validation

After a client context is created, applications are ready to validate the client's access to a requested application resource. For an Authorization Manager application, this can be done in two ways.

Access Check

When the user makes a request, the application maps the request to the required operations and determines the scope in which the user is making the request (as discussed in the Determine Scoping Model section of this paper). Then the application calls AccessCheck and sends the following parameters:

String for security event log audit records   This is a string that will give the audit administrator more information about the security event. For example, you could put the name of a high-level application activity such as "Submit-Expense" here to inform the audit administrator of the high-level activity that the user is performing.

Scope array   This is an array of scope names. In version 1, Authorization Manager only supports one scope name and it must be in the first element of the array. The application maps the requested resource to the correct scope based on the scoping logic the application implemented.

Operation IDs array   This is an array of operation IDs. The application must map the requested action to one or more operations. Each operation has a corresponding ID value. Each value is passed in as a separate element of the operations array.

BizRule Parameter names array (optional)   BizRule parameters are passed to AccessCheck via two arrays. The first is the BizRule parameter names array. In this array you must send in each BizRule parameter name (a text string) as a separate element. This must be alphabetized in case insensitive ASCI order. For example, BizRule parameters with names starting with a letter at the beginning of the alphabet come before BizRule parameters with names that start with a letter near the end of the alphabet. The BizRule names array must correspond (index to index) with a value in the BizRule Parameter values array.

BizRule Parameter values array (optional)   This is an array of values corresponding to the name specified in the BizRule Parameter name array. Each BizRule parameter value must be placed in an array corresponding to the BizRule Name for each value.

BizRule COM interface names array (optional)   If you want to use code in the application and call it from a BizRule (useful if you want to do more complex computation in BizRules) you can pass in the COM interface names and values to AccessCheck and call them from the BizRule. The BizRule COM interface name parameter works similarly to the BizRule Parameter names array. It is an array of names that will be used in the BizRule to reference the COM interface implement by the application. The BizRule COM interface name array must correspond, index to index, with the BizRule COM interfaces value array.

BizRule COM interface values array (optional)   This is an array of interfaces corresponding to the name specified in the BizRule Parameter name array. Each BizRule parameter value must be placed in an array corresponding to the BizRule Name for each value.

The results of the AccessCheck are returned from the AccessCheck method call in the Results array. Results are given per operation requested in the operations array specified in the AccessCheck call. To determine if access has been granted, analyze the value in each element of the Results array corresponding to each operation you requested.

Sample code using AccessCheck is provided in the Windows SDK. Search on Microsoft.com for information about downloading or obtaining the Windows SDK. Samples are added or updated on each release of the SDK.

Role Query and Authorization

After a client context is created, applications have the option of querying role information about the user in order to present a user interface based on the user's role memberships. For example, a Web-based application may render a Manager user interface for people in the Manager role, and an interface based on the employee role for everyone else. The IAzClientContext2::GetRoles method enumerates the user's role memberships. The set of roles that is returned from GetRoles is based only on explicit membership assignment; GetRoles does not evaluate BizRules. This function is often used to mimic the IsInRole capability of the built-in principal but at the loss of pivot ability.

Additionally, applications that use scopes may wish to enumerate the scopes in which the client has been assigned any access. Applications that have a capability-based user interface, or need to query the client's permissions across the application for reporting or exporting to another tier can use the IAzClientContext2::GetAssignedScopesPage method. If more granularity is needed after enumerating the assigned scopes, the application can call IAzClientContext2::GetRoles for each assigned scope. Likewise, from each role, tasks and operations can be enumerated if needed.

Updating Policy

In order to receive policy changes, the application must have a periodic call to IAzAuthorizationStore::UpdateCache. The IAzAuthorizationStore::UpdateCache method will check to see if a policy change has taken place since the policy in memory was last refreshed. If no change has taken place, no policy is updated. If the policy has changed, the IAzAuthorizationStore::UpdateCache method will reload the policy differently on the XML store than on the Active Directory and ADAM stores. The XML store will be completely reloaded into memory. For the Active Directory and ADAM stores, which use the delay-load technique discussed above, IAzAuthorizationStore::UpdateCache will refresh only the applications and scopes that have been previously opened. Over time an application may load many scopes, and it is possible that calling the IAzAuthorizationStore::UpdateCache will result in a delay while each application and scope in memory is refreshed. Applications have the option of occasionally closing and reopening the application (by calling the IAzAuthorizationStore::CloseApplication and then IAzAuthorizationStore::OpenApplication method) to free all policy in an application. This will result in faster calls to IAzAuthorizationStore::UpdateCache. However, doing this will result in a delay on the next call to access check to load the policy of the specified scope. Applications that will have large numbers of scopes should periodically call IAzAuthorizationStore::CloseApplication or expose configuration options allowing an application administrator to configure the frequency of IAzAuthorizationStore::UpdateCache as well as closing and reopening the application.

Performance

Application initialization and access validation performance can vary, depending on how applications use features such as XML storage, BizRules, and Authorization Manager application groups. Application designers have several options to leverage in meeting performance requirements.

Application Initialization Performance

Initialization performance for large stores improves when you use the Active Directory or ADAM store rather than the XML Authorization Manager storage provider. When an application initializes a connection to an Authorization Manager policy store, authorization policy is loaded into the address space of the application. The amount of policy that is loaded depends on the store type.

The Active Directory or ADAM Authorization Manager policy store allows Authorization Manager application objects to be loaded and unloaded on demand and the Authorization Manager scope objects to be loaded only when needed.

The XML Authorization Manager storage provider loads the entire authorization policy into memory.

Additionally, the time it takes to initialize increases as the number of objects within the Authorization Manager policy store increases or as the number of members in Authorization Manager application basic groups increases. To minimize the size of application groups, use Active Directory groups when possible, and then use these Active Directory groups in Authorization Manager application groups or roles.

See Updating Policy in the previous section for information on how to improve policy updating performance.

Important   Authorization Manager groups are not meant to replace Active Directory groups. They provide a solution to application administrators who want some control of groups within the application due to the latency or political issues often associated with managing Active Directory groups in large organizations. It's recommended that application groups are unions, concatenations, or subtractions of Active Directory groups and ideally < 1000 members. For groups with higher numbers of members, applications should use normal Active Directory group memberships. 

Access Validation Performance

Access validation includes the two-step process of initializing a client context and calling AccessCheck (or a query such as GetRolesForUser.) To improve performance in access validation you can first look at ways to optimize the client context initialization step, and then examine how to optimize the calling of AccessCheck.

The first step in improving the performance of initialization of client context is to only do it when you have to. Many applications may have expected usage patterns that would benefit from caching the client context. Additionally, using the token-based or manually created client context initialization mechanisms when possible can help improve performance. See Client Context Initialization in the previous section, for information about using the best client context initialization method and caching the client context.

An important thing to remember is that you do not need to initialize a connection to the Authorization Manager policy store and open an application on every client request. Store initialization should be done only once at application initialization; while OpenApplicaion is done once or rarely depending on the policy updating method you use (See Updating Policy for information on choosing a policy updating approach.)

Understanding the Authorization Manager AccessCheck method provides insight into how to optimize access validation performance.

The AccessCheck method performs the following steps:

  • Identifies scopes that apply to the AccessCheck routine, both within the specified scope and the global scope.
  • Enumerates the set of roles in the scopes from step 1.
  • Discards roles that do not refer to requested operations. If RoleForAccessCheck is specified, all roles other than that specified are ignored.
  • Verifies the client security context's membership in each role.
  • If the client is a member of the role in step 4, AccessCheck records all requested operations that are assigned to the role as granted.
  • Repeats steps 3-5 for each role in applying scopes until all requested operations are granted.

The above logic is repeated a maximum of three times.

On the first pass, AccessCheck ignores any tasks containing BizRules and application groups containing LDAP queries. Both BizRules and LDAP queries can take a long time to compute and can require network operations, so the first pass takes place in case they are not needed.

In the second pass, AccessCheck processes BizRules, but still ignores LDAP query groups. BizRules are supposed to be simple and may not involve network operations, so they are likely to be faster to process than LDAP queries.

The third pass processes all data, including LDAP queries, using the cached results from previous iterations. AccessCheck terminates as soon as access is granted to all of the requested operations.

Since AccessCheck is optimized to evaluate static memberships ahead of dynamic memberships, and tasks without BizRules ahead of tasks with BizRules, you can optimize performance by using static assignments where possible.

Authorization Manager application groups are not evaluated until AccessCheck is called and membership in a role is queried. (This process of group evaluation is also called "late-bound.") For LDAP query application groups, once membership in an application group is established for a user, that user's membership is cached for the life of the user context object or until IAzAuthorizationStore::UpdateCache is called. Because of this, subsequent AccessChecks that involve group evaluations speed up as group memberships are resolved and cached. Caching is done on a per AzAuthorizationStore basis. Calls on multiple threads will result in multiple or distinct caches.

All caching is removed when the last reference to a store is gone. 

As a best practice, BizRules should be kept computationally small. BizRules offer the greatest degree of flexibility in an AccessCheck call, and therefore offer the greatest potential to decrease performance. Network bound BizRules decrease the performance of AccessCheck. Often this cannot be avoided — for example, if a database needs to be queried to check a credit history (but such queries will delay the result to the user).

Environment-Specific Design Considerations

Authorization Manager is designed for applications using the trusted subsystem application model, where access checks are done in the context of the server, and the application has enough access to application resources to service the request possible through the application. If access to ACLed resources is given through the application, the administrators of the ACLed resources only need to grant access to the application server service account. (For more information, see Trusted Subsystem Application Model earlier in this paper.) To use a trusted subsystem model, usually the application server will run in the context of a dedicated service account. Since the application needs to read authorization policy information from the Authorization Manager policy store, the security context that the application runs in needs read permission to the store. You can do this by designating the dedicated service account as a Reader of the Authorization Manager policy store. To do this, you can use the Authorization Manager MMC (Microsoft Management Console).

If impersonation is used in the application, you should not impersonate a client while calling Authorization Manager API. The only exception to this is when you wish to initialize a client context from the impersonated thread context. In that case, call the IAzApplication2::InitializeClientContextFromToken method and specify 0 for the nullTokenHandle parameter. The other Authorization Manager interfaces read the Authorization Manager authorization policy store (which contains user role information), and the IAzApplicatin::AccessCheck function runs BizRules and resolves LDAP query groups. Since a user requires a high degree of access to be able to read authorization policy and perform BizRule script actions, you should not call these functions while impersonating the security context of the user. Each Windows programming environment gives you different options to use to manage impersonation and call Authorization Manager from a service account for the application.

Managed Code

There is a Primary Interop Assembly (PIA) provided to access the Authorization Manager Common Object Model (COM ) object from the .NET Common Language Runtime (CLR). For more information about the Authorization Manager PIA version 1.2, see the end of this paper for a graphical description. For more in general about PIAs, see Primary Interop Assemblies (PIAs)https://msdn.microsoft.com/library/en-us/dndotnet/html/whypriinterop.asp.

ASP.NET

Application servers written in ASP.NET can use the Authorization Manager API through the .NET Interop assembly that is included in Windows Server 2003 in the %windir%\Microsoft.Net\AuthMan directory. The Authorization Manager Runtime for Windows 2000 (available at Microsoft Download Center) contains the interop assembly for Windows XP and Windows 2000 deployments. Use the latest version available unless the application requires an earlier version. Applications may also choose to create a custom interop assembly using the TLBImp.exe tool in the .Net Framework SDK.

To use the trusted subsystem model in ASP.NET, you need to run the application server in a service account. To configure your ASP.NET application server to run in a service account, you create a separate IIS 6.0 worker process which runs in the security context of the service account created for the application server. You can then configure your ASP.NET application to use this dedicated IIS worker process. For more information on IIS 6.0 worker processes, see the Microsoft Internet Information Services page.

Alternatively, you can use ASP.NET to configure the context in which an application runs using the web.config file for your ASP.NET application. In that case, you should configure the ASP.NET application to run as the dedicated service account.

When your ASP.NET application is initialized, it can call AzAuthorizationStore.Initialize to connect to the Authorization Manager store. It then calls AzApplication.Initialize to load an initial percentage of the applications authorization policy.

At runtime, when a client connects, you have the option of initializing a user context from the client's account name or from a token representing the client. The token can be retrieved by way of the ASP.NET HttpWorkerRequest object. Authorization Manager can also initialize a context from a token using the IIdentity interface of a WindowsPrincipals. If a token is unavailable, a SID or Name (UPN or DOM\Uid) may be used. It is recommended that you initialize the Authorization Manager context from a token when possible so that you do not have to potentially query the user's account object. That may require network-bound queries to Active Directory and also that the application service be explicitly given permission in Active Directory to enumerate a user's group membership. We are assuming that Active Directory is the authentication store here; however, environment-specific details will be covered later regarding the ADAM and ADFS where creating an empty client context would be preferred.

Figure 16. Typical single server Authorization Manager object lifecycle

The client context would not be held in process in a Web garden scenario. The client context would be created per call in that case. Caching data used to create the client context may be possible (such as the roles and groups where subsequent calls could start with an empty context), and the previously acquired data may be applied to the security context on the page return.

The following code calls the Authorization Manager API through ASP.NET, using a .NET Interop assembly to approve an expense approval. This code is a simple Web expense application that runs in a dedicated service account. The Authorization Manager policy store for this application may be created from the VBScript install script that appears in this paper.

<%@ Page Language="C#" Debug="True"%> 
<%@ Assembly Name ="Microsoft.Interop.Security.AzRoles"%> 
<%@ Import Namespace="Microsoft.Interop.Security.AzRoles"%> 
<%@ Import Namespace="System.Runtime.InteropServices" %> 
<html> 
   <head> 
   </head> 
   <body> 
      <% 
      Microsoft.Interop.Security.AzRoles.AzAuthorizationStoreClass 
AzManStore = 
            new 
Microsoft.Interop.Security.AzRoles.AzAuthorizationStoreClass(); 
      // Keep the authorization store in a safe place out of the Web 
space. 
      AzManStore.Initialize(0, 
@"msxml://D:\SecureDir\MyWebAppsAzStore.xml", 
                            null); 
      IAzApplication2 azApp = 
AzManStore.OpenApplication2("expense",null); 
      HandleRef token = new HandleRef(this, ((HttpWorkerRequest) 
((IServiceProvider)Context).GetService(typeof(HttpWorkerRequest))).GetU 
serToken()); 
//-------------- Create Client Context -------------- 
      IAzClientContext context = 
azApp.InitializeClientContextFromToken( 
                        (UInt64)token.Handle, 
                        0); 
      object[] scope = new Object[1]; 
      scope[0] = ""; 
      object[] operations = new Object[1]; 
      operations[0] = 55; 
//--------------- Do access check -------------------- 
//Set up BizRule params. To optimize performance, name/value pairs must 
//be placed in array alphabetically 
      Object[] BRNames = new Object[1]; 
      BRNames[0] = "ExpAmount"; 
      Object[] BRValues = new Object[1]; 
      BRValues[0] = Convert.ToInt32(Request.QueryString["Amount"]); 
      object[] results = (object[]) context.AccessCheck("Approve", 
                                                        scope, 
                                                        operations, 
                                                        BRNames, 
                                                        BRValues, 
.null,null,null); 
      bool bAuthorized = true; 
      foreach (int iResCode in results) 
      { 
         if ( iResCode != 0 ) // zero = no error 
         { 
           bAuthorized = false; 
           break; 
         } 
      } 
      Response.Write((string)context.UserSamCompat + "<br>"); 
      Response.Write( 
             "Approval of " + Convert.ToString(BRValues[0]) + " was"); 
      if (bAuthorized) { 
         %><font color="#008000"><b>APPROVED</b></font><% 
      } else { 
         %><font color="#FF0000"><b>DENIED</b></font>.<% 
      } 
      %> 
   </body> 
</html> 

Some tips for working with COM Interop may be found on the Office interoperability pages. It is preferable to use the ReleaseComObject in a loop until all references have been decremented.

Best Practices

To summarize this section, we have three basic choices for cleaning up COM objects:

.NET garbage collection   This is controlled in a deterministic manner by using GC.Collect and GC.WaitForPendingFinalizers (twice).

ReleaseComObject   This is highly deterministic but somewhat problematic to use, and it is potentially dangerous because it affects the target RCW for the entire AppDomain.

AppDomain isolation   This means isolating our interop functionality in a separate AppDomain, which we can later unload altogether, thereby releasing all RCWs in that AppDomain.

For more details, see Microsoft .NET Development for Microsoft Office.

Some developers prefer to wrap Authorization Manager with a .NET Enterprise Services/COM+ server application where the store is opened in the constructor. The wrapper objects are then set for object pooling and continuously running with no timeout.

Note   XML Stores get fully loaded into memory. Active Directory and ADAM stores are loaded based on need (also called "lazy-load" as described above). Each object would have a cache of the store independent of the others. That cache would be based on access. Authorization Manager is not a COM+ object; however, it is a thread-safe object.

Sample Expense Console Application

Module Module1

    Sub Main()
        Dim App As IAzApplication2
        Dim AzManStore As New AzAuthorizationStore
        ' This is the old Client Context
        'Dim CChandle As IAzClientContext
        Dim CCHandle As IAzClientContext2
        Dim Names(1) As Object
        Dim Values(1) As Object
        Dim Scopes(1) As Object
        Dim Operations(1) As Object
        Dim Results(1) As Object

  'Initialize the store
  'AzManStore.Initialize(0, "msxml://c:\expense.xml") 'Policy Store in XML
        AzManStore.Initialize(0, "msldap://eric2:50000/cn=testapp,cn=applications,dc=contoso,dc=com")
'Policy in ADAM

        'Open Application
        'App = AzManStore.OpenApplication2("Expense")
        App = AzManStore.OpenApplication2("ADAM Test App")

        'Request User Type
        Console.WriteLine("What Type of User?")
        Console.WriteLine(
            "(4 for ADAM User with role assign)" & vbCrLf & 
            "(3 for ADAM User (StringSID))" & vbCrLf & "(2 for ADAM User w/group)" &
            vbCrLf & "(1 for Administrator)" & vbCrLf & "(0 for User)")

        'Read User Type
        Dim UserType As Integer = 0
        UserType = CInt(Console.ReadLine())

        'Create User Context
        Select Case UserType
            Case 1
                CCHandle = App.InitializeClientContextFromName("Administrator",
                    "e-contoso", 0)
            Case 2
                CCHandle = App.InitializeClientContext2("ADAM Test")
                Debug.WriteLine("Old LDAP path: " & CCHandle.LDAPQueryDN)
                CCHandle.LDAPQueryDN = 
"ldap://eric2.erichue.contoso:50000/cn=logonuser,ou=users,dc=contoso,dc=com"
                Dim Sids(2) As Object
                Sids(0) = 
"S-1-390129220-1118192347-3636959150-1159784139-1083089578-2007072967"
                Sids(1) = "S-1-390129220-1118192347-513"
                CCHandle.AddStringSids(Sids)
            Case 3
                'This worked for ADAM w/o groups
                CCHandle = App.InitializeClientContextFromStringSid(
"S-1-390129220-1118192347-3636959150-1159784139-1083089578-2007072967", 1)
            Case 4
                CCHandle = App.InitializeClientContext2("ADAM Test")
                Dim AddRoles(1) As Object
                AddRoles(1) = "Expense Users"
                AddRoles(0) = "Expense Admins"
                'CCHandle.AddRoles(AddRoles, "AllRoutines")
                ' Note: if you are using a blank scope with AddRoles, then you
                ' MUST use Nothing rather than ""!!!!
                CCHandle.AddRoles(AddRoles, Nothing)
            Case Else
                ' This is the original
                CCHandle = App.InitializeClientContextFromName("ExpenseUser", 
                    "e-contoso", 0)
        End Select

        Dim Role As Object
        Dim Roles() As Object
        Dim counter As Integer = 0
        'Roles = CCHandle.GetRoles("AllRoutines")
        Roles = CCHandle.GetRoles("")
        Dim CheckRoles As Array = Roles

        If CheckRoles.Length = 0 Then
            Console.WriteLine("You have no roles.")
        Else
            For Each Role In Roles
                Console.WriteLine("Your role is " & Roles(counter) & ".")
                counter = counter + 1
            Next
        End If

        'Request Expense Amount
        Console.WriteLine("Amount of Expense?")

        'Read in the expense
        Dim ExpenseAmount As Integer = 0
        ExpenseAmount = CInt(Console.ReadLine())

        'Set the values of the call to check access
        Names(0) = "ExpAmount"
        Values(0) = ExpenseAmount
        'Scopes(0) = "AllRoutines"
        'Operations(0) = 62
        Scopes(0) = ""
        Operations(0) = 1

        'Call Access Check 
        Results = CCHandle.AccessCheck("Expense", Scopes, Operations, Names, Values)

        'Check the Resuls from submit
        If Results(0) = 0 Then
            Console.WriteLine("Your Expense is approved.")
        Else
            Console.WriteLine("Your expense is not approved.")
        End If

        'Request to view Reports
        Console.WriteLine("Would you like to view a report?")

        ' Read the Response
        Dim ReportResponse As String
        ReportResponse = UCase(Chr(Console.Read()))

        If ReportResponse = "Y" Then
            'set the values for the access check
            Results = Nothing
            'Scopes(0) = "AllRoutines"
            'Operations(0) = 61
            Scopes(0) = ""
            Operations(0) = 2

            'Call Access Check 
            Results = CCHandle.AccessCheck(
                "Expense", Scopes, Operations, Nothing, Nothing)

            'Check for results
            If Results(0) = 0 Then
                Console.WriteLine("Report Accessed")
            Else
                Console.WriteLine("Report Access Denied")
            End If

        Else
            Console.WriteLine("Report Not Requested")

        End If
        Console.ReadLine()

    End Sub

End Module

There are articles to assist with using forms authentication against Active Directory, such as How To: Use Forms Authentication with Active Directory or How To: Use Membership in ASP.NET 2.0.

Step 1. Configure Forms Authentication

Set the <authentication> element's mode attribute to "Forms" and configure your application's web.config file as shown in the following example.

<authentication mode="Forms">
    <forms loginUrl="Login.aspx"    
           protection="All"                     
           timeout="30"                         
           name="AppNameCookie"                                 
           path="/FormsAuth"                      
           requireSSL="true"                    
           slidingExpiration="true"             
           defaultUrl="default.aspx"
           cookieless="UseCookies"
           enableCrossAppRedirects="false"/>
 </authentication>

Description

  • logonUrl points to the logon page. Ideally, you should place this in a folder that requires Secure Socket Layer (SSL) for access.
  • protection is set to "All" to specify privacy and integrity for the Forms authentication ticket.
  • timeout is used to specify a limited session lifetime.
  • name and path are set to unique values for the current application.
  • requireSSL is set to "true" to specify that the authentication cookie should only be transmitted over an SSL-protected channel.
  • slidingExpiration is set to "true" to enforce a sliding session lifetime. This means that the timeout is reset after each request to your application.
  • defaultUrl is set to the Default.aspx page for the application.
  • cookieless is set to "UseCookies" to specify that the application uses cookies to send the authentication ticket to the client.
  • enableCrossAppRedirects is set to "false" to indicate that the application cannot redirect requests outside the application scope.

Add the following <authorization> element beneath the <authentication> element. This permits only authenticated users to access the application. The previously established logonUrl attribute of the <authentication> element will redirect unauthenticated requests to the Logon.aspx page.

<authorization> 
   <deny users="?" />
   <allow users="*" />
 </authorization>

Step 2: Configure ActiveDirectoryMembershipProvider

Configure the ActiveDirectoryMembershipProvider in your application's web.config file as shown in the following example:

<connectionStrings>
  <add name="ADConnectionString" 
   connectionString=
    "LDAP://domain.testing.com/CN=Users,DC=domain,DC=testing,DC=com" />
 </connectionStrings>


 <system.web>
 ...
 <membership defaultProvider="MembershipADProvider">
  <providers>
    <add
      name="MembershipADProvider"
      type=
"System.Web.Security.ActiveDirectoryMembershipProvider, System.Web, 
Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"
                connectionStringName="ADConnectionString"
                connectionUsername="<domainName>\administrator" 
                connectionPassword="password"/>
   </providers>
 </membership>
 ...
 </system.web>

Note   Make sure to set the defaultProvider attribute value to your ActiveDirectoryMembershipProvider. You need to do this to override the machine-level default which uses the SQL ServerMembershipProvider that uses the local SqlExpress instance.

In addition to the above attributes, the ActiveDirectoryMembershipProvider has attributes that you can optionally overwrite. For more information, see Configure ActiveDirectoryMembershipProvider in this document.

Step 3: Create Users

You can create new users in the following ways:

  • Use the Administration Tool, which provides a wizard-like interface for creating new users.
  • Create an ASP.NET page that contains a CreateUserWizard control to create a new user. This control uses the configured membership provider to encapsulate the logic of creating a new user.
  • Create an ASP.NET Web page that contains TextBox controls to obtain the user name and password (and optionally an e-mail address), and then use the CreateUser Membership API method to programmatically create the new user.

Note   All of these techniques use the Membership.CreateUser method.

The default configuration for the ActiveDirectoryMembershipProvider uses User Principal Names (UPNs) for name mapping, as shown below:

attributeMapUsername="userPrincipalName"

Therefore, all user names must have the following format:

UserName@DomainName

If you call CreateUser programmatically, use this format:

Membership.CreateUser("UseName@DomainName",
                      "Secret",
                      "userName@emailAddress"); 

You can change the user mapping type by setting the following attribute in the Membership Provider configuration in the web.config file.

attributeMapUsername="sAMAccountName"

With this configuration, supply user names in the following format:

UserName

For example:

Membership.CreateUser("UserName", 
                      "Secret", 
                      "userName@emailAddress")

Note   You should set the requiresUniqueEmail attribute to "true" to ensure that users supply unique e-mail addresses.

Step 4: Authenticate Users

To authenticate users, you must provide a logon form. This could be a separate page or a special area on your application's home page.

There are two ways to do this:

  1. You can use the new ASP.NET 2.0 logon controls.

    The ASP.NET logon controls encapsulate nearly all of the logic required to obtain credentials from users and to validate them against a user store. They use the configured membership provider. You do not need to write any additional code.

    After the user is validated, the logon controls automatically save information about the user, for example, by using an encrypted cookie if the user's browser accepts cookies.

  2. You can create custom logon forms by using ASP.NET TextBox controls.

    If you have created a custom logon form with simple TextBox controls, you can prompt the user for a user name and password, and then call the ValidateUser method of the Membership class to perform the validation.

You also need to call methods of the FormsAuthentication class to create the cookie and write it to the user's computer as shown below.

if (Membership.ValidateUser(userName.Text, password.Text))
 {
  if (Request.QueryString["ReturnUrl"] != null)
  {
    FormsAuthentication.RedirectFromLoginPage(userName.Text, false);
  }
  else
  {
    FormsAuthentication.SetAuthCookie(userName.Text, false);
  }
 }
 else
 {
  Response.Write("Invalid UserID and Password");
 }

Note   Both of these techniques use the Membership.CreateUser method.

The default ActiveDirectoryMembershipProvider configuration uses user principal names (UPNs) for name mapping. If you call the Membership.ValidateUser method, be sure to use a UPN name as shown here:

bool isValidUser = Membership.ValidateUser(
                                  "UseName@DomainName", 
                                  "Secret"); 

You can change the mapping for users by setting the following attribute in Membership Provider configuration in web.config file.

 attributeMapUsername="sAMAccountName"

With this configuration, you must call ValidateUser as shown here:

bool isValidUser = Membership.ValidateUser("UserName", 
                                           "Secret", 
                                           "userName@emailAddress")

The above scenario would utilize the Authorization Manager InitializeClientContextfromName. Providing the UPN to the Authorization Manager client context is enough for Authorization Manager to query the domain and load the SIDs to the context.

Any time that a client context is populated via SID or name is a prime candidate for caching for future calls. If on a single server, cache the client context InProc (also known as In Process). If on a farm, then initializing the client context per call will be required.

The Authorization Manager Role Provider Capability in ASP.NET 2.0

The Role Provider for ASP.NET 2.0 provides a standard way to start working with RBAC and Authorization Manager and to configure applications using the ASP.NET IsCallerInRole model to be stored and managed in a common authorization store with applications using Authorization Manager interfaces.

The following introduction utilizes the common approach; however, the Authorization Manager object is exposed directly through the role provider and the developer can make use of the advanced capabilities in Authorization Manager.

Figure 17. Role assignments

Sample store – Create a simple store, for example an XML store called mystore.xml. Create a new application, such as App1. Create the following empty role definitions in the App1 application: foo, foo1, foo2, and foo4.

Sample ASP.NET 2.0 Authorization Role Provider web.config

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.web>
     <authorization>
          <deny users="?" />
     </authorization>

     <roleManager enabled="true"
           defaultProvider="AuthorizationStoreRoleProvider">
     <providers>
     <clear />
                                                
     <add 
          connectionStringName="AuthorizationStoreRoleProviderConnection"
          applicationName="App1" 
          name="AuthorizationStoreRoleProvider"
          type=
"System.Web.Security.AuthorizationStoreRoleProvider, System.Web, 
Version=2.0.0.0, Culture=neutral, publicKeyToken=b03f5f7f11d50a3a"
     />

     </providers>

     </roleManager>
</system.web>
<connectionStrings>
<add name="AuthorizationStoreRoleProviderConnection" connectionString="msxml://c:\mystore.xml" />
</connectionStrings>
</configuration>
 

The above sample web.config adds the Authorization Manager role provider and connection to the store (if using Active Directory or ADAM use the msldap:// syntax).

The web.config may be edited using the new graphical tool with ASP.NET 2.0, which displays this web.config as follows:

Figure 18. ASP.NET AzMan role provider

To manage the configuration from the MMC for IIS, right-click the virtual directory and select Properties.

Figure 19. ASP.NET configuration

The default connection strings are located here. Note that the AuthorizationStoreRoleProvider specifies an Authorization Manager XML authorization store.

Figure 20. ASP.NET authorization rules

Deny the Anonymous User if you are gaining access to the Windows principal after the On Authenticate portion of the Global.asax ASP.NET execution chain. Otherwise, the Windows principal gets stripped prior to the page execution.

Figure 21. ASP.NET authentication mode

Set the authentication to Windows and enable Role Management for the AuthorizationStoreRoleProvider. The Membership provider is unrelated except if using Forms authentication, when it produces a token that be may used to initialize the Authorization Manager client security context.

Figure 22. Role Provider selection

Click the Manage providers button to set the Authorization Manager application. Our sample below shows that we are App1.

Figure 23. Provider settings

The connectionStringName refers to the settings seen previously; the applicationName App1 refers to the application App1 in the Authorization Manager policy store.

Figure 24. Authorization Manager role assignments

This represents the starting state of the authorization policy store. The store is XML and located at C:\mystore.xml with App1 and 4 roles.

The ASP.NET Authorization provider enables the abstraction of role assignment. The programmer can check if the user is in a role or set of roles at runtime. An application administrator could assign users to these roles in the Authorization Manager.msc seen in the graphic above.

Sample ASP.NET 2.0 Page

The following ASP.NET 2.0 sample demonstrates the authorization provider capabilities from code at the page level. We will return the application name that we hold open when we open the store, the name is returned from the identity object, a check is performed to make sure that the user is in a given role, all the roles in the authorization store are listed, only the user's roles are listed, then a role is added and removed from the store.

<%@ Page LANGUAGE="C#" Debug="true" %>
<html>
<body>
<form runat=server>
<asp:Literal runat="server" id="Literal1" Text=""></asp:literal>
</form>
<script runat="server" >
public void Page_Load()
{
Response.Write(
     "Application Name: " + Roles.ApplicationName + "<br>");
// Sample output: Application Name: App1
Response.Write(
     "User=" + User.Identity.Name + ", IsUserInRole(\"foo2\")  : " +
     Roles.IsUserInRole("foo2") + "<br>");
// Sample output: IsUserInRole("foo2") : True
Response.Write(
     "Role foo2 exists:" + Roles.RoleExists("foo2") + "<br>");
// Sample output: Role foo2 exists:True

Response.Write(
     "Role foo3 exists:" + Roles.RoleExists("foo3") + "<br>"); 
// Sample output: Role foo3 exists:False
string[] myroles = Roles.GetAllRoles();
Response.Write(
     "No of Application Roles: " + myroles.Length + "<br>");
// Sample output: No of Application Roles: 4

for(int i = 0; i < myroles.Length; i++)
{
Response.Write("Role: " + myroles[i] + "<br>");
}
/*
Sample output:
Role: foo
Role: foo1
Role: foo2
Role: foo4
*/

Response.Write("<HR><HR>");
string[] myrolesfu = Roles.GetRolesForUser();
Response.Write("No of Roles For User : " + myrolesfu.Length + "<br>");
//Sample output: No of Roles For User : 1

for(int i = 0; i < myrolesfu.Length; i++)
{
Response.Write("Role: " + myrolesfu[i] + "<br>");
//Sample output: Role: foo2
}
Response.Write("<hr />Creating role foo3<br />");
Roles.CreateRole("foo3");
Response.Write("Role foo3 exists:" + Roles.RoleExists("foo3") + "<br>");
//Sample output: Role foo3 exists:True
Roles.DeleteRole("foo3");
Response.Write("Role foo3 exists after DeleteRole: " + 
Roles.RoleExists("foo3") + "<br>");
//Sample output: Role foo3 exists after DeleteRole: False
}
</script>
</body>
</html>

ASP.NET Sample Output

Review the store pictured above to correlate the output, and see the comments in the code above reflected in the output page below. 

Application Name: App1
User=DEMO\Administrator, IsUserInRole("foo2") : True
Role foo2 exists:True
Role foo3 exists:False
No of Application Roles: 4
Role: foo
Role: foo1
Role: foo2
Role: foo4
 
----------------------------------------------------------------------------
 
----------------------------------------------------------------------------
No of Roles For User : 1
Role: foo2
 
----------------------------------------------------------------------------
Creating role foo3
Role foo3 exists:True
Role foo3 exists after DeleteRole: False

You can find more information in the Prescriptive Architecture Group (PAG) article, How To: Use Authorization Manager (AzMan) with ASP.NET 2.0

Enterprise Library

The patterns & practices Enterprise Library is a library of application blocks designed to assist developers with common enterprise development challenges. Application blocks are a type of guidance provided as source code that can be used "as is," extended, or modified by developers to use on enterprise development projects. Enterprise Library features new and updated versions of application blocks that were previously available as stand-alone application blocks. All Enterprise Library application blocks have been updated with a particular focus on consistency, extensibility, ease of use, and integration. Enterprise Library is available for download from https://msdn.microsoft.com/library/en-us/dnpag2/html/entlib.asp

Note   You need to join the patterns & practices Workspaces community to download additional samples and presentations.

The following is a very simple introduction to the Authorization Manager Authorization Provider in Enterprise Library:

  1. Install Enterprise Library.
  2. Create a new Web project (Visual Studio 2003, .NET 1.1, C# example below).
  3. Add the Enterprise Library projects to the solution.
  4. Add references to EntLib from the Web project.
  5. Write the Web form code.
//Patterns and Practices 
using Microsoft.Practices.EnterpriseLibrary.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Security;
using Microsoft.Practices.EnterpriseLibrary.Security.Authorization;
using Microsoft.Practices.EnterpriseLibrary.Security.AzMan;
 
namespace WebExpenseAzManEnt
{
public class WebForm1 : System.Web.UI.Page
{
// Windows Principal
private WindowsPrincipal aprincipal; 
// Provider
private IAuthorizationProvider azman;
private void Page_Load(object sender, System.EventArgs e)
{
   //This uses the default provider. Another approach is GetAuthorizationProvider("AzManDepartmentScope")
   // where the scope is set in configuration.
this.azman =  AuthorizationFactory.GetAuthorizationProvider(); 
aprincipal = new WindowsPrincipal(WindowsIdentity.GetCurrent()); 
// Assign aprincipal.Identity.Name to the Greeting Label 
//Assign false as a default and assign the result of the check T or F
HyperLink1.Visible =  azman.Authorize(aprincipal, "O:Administer");
HyperLink2.Visible =  azman.Authorize(aprincipal, "O:Approve");
HyperLink3.Visible =  azman.Authorize(aprincipal, "O:ListExpenses"); 
                    }
  }
}

Note   There is a limitation in Enterprise Library 1.0 that prevents you from natively passing multiple operations for Access Checks. For instance, an application with 100+ fields on a given page where each field's visibility property was set based on the result of an access check could be performed with one call, whereas the current object model requires individual access checks per Authorize call. Also, the current version requires a Windows Principal or token more specifically; however, this is not required as of Windows 2003 Service Pack 1. Enterprise Library exposes a robust wrapper approach to role-based authorization through the use of operations in access checks, which aids maintainability with the operations and role assignment located in the policy store.

Let's explore some of the components that make up our Enterprise Library sample. The first part is a store where we show our role assignment for this sample.

Figure 25. Administrator role

This case shows the Administrator's group assigned to a role "Administrator."

Figure 26. ASP.NET role memberships

The graphic above is the Enterprise Library Configuration Tool. Open web.config, add the Authorization Manager provider configuration details, and set the default provider. Each provider may be referenced by name in the getprovider call.

Figure 27. Role query

Add the dependencies from Enterprise Library (create a copy of the ones that were installed on the machine). The panel on the right above shows the dependencies.

The Web project called WebExpenseAuthorization ManagerEnt is the only project that was created. Add a few aspx pages for targets of the links.

Figure 28. Administrator view

Run the sample test as Administrator and pass the access check. Notice that no other links are present. This is due to no role assignment for Administrators to act as submitters or approvers in this application.

Active Directory Application Mode (ADAM)

In Windows Server Service Pack 1, Authorization Manager is updated to provide integration support for using ADAM principals. This section describes how to integrate ADAM principals with Authorization Manager applications; this way user accounts can be stored in ADAM and applications can use Authorization Manager to authorize those users. See Using ADAM as a Store for Authorization Manager Policy in the next section of this paper for information on how to create and store Authorization Manager policy in ADAM.

Overview of ADAM Security Principals

ADAM security principals are users or groups that are created and reside in the ADAM directory. ADAM security principals can be created based on the importable user classes (including inetOrgPerson) that are provided with ADAM. ADAM security principals can be created from any object class in the schema that contains the SecurityPrincipal static auxclass and the unicodePwd attribute. ADAM assigns each ADAM security principal a unique SID, which is guaranteed to be unique across all networks.

Like Active Directory principals, ADAM principals can be assigned to groups (ADAM groups) and have credentials (username and password). Unlike Active Directory principals, ADAM principals cannot log on to a Windows desktop or file shares, or be authenticated through Windows Integrated Authentication. This means that applications that use ADAM principals need to authenticate the user credentials and query group memberships manually using LDAP interfaces. Typically applications authenticate ADAM principals using the ldap_bind API or a higher level wrapper such as Active Directory Services Interfaces (ADSI.) and query a user's groups by querying the user's tokenGroups attribute.

Using ADAM Principals in Authorization Manager

This is done by adding interfaces which allow applications to create an Authorization Manager empty context, adding the SIDs (user and group) to that context, and finally adding the ability to set the distinguished name of the ADAM principal for use by Authorization Manager dynamic LDAP query groups. A custom management user interface is utilized to add the ADAM user and group SIDs to the role assignment.

There are two methods of authorizing ADAM principals with Authorization Manager.

The first approach checks for a SID match using the access check. It is faster as it requires no searches. This approach is preferred in most development efforts due to speed and involves the following:

The applications that have authenticated an ADAM principal query the ADAM principal's user and group SIDs in ADAM (done by using LDAP to query the principal's tokenGroups attribute). The application adds the SIDs to an Authorization Manager client context via the IAzClientContext2::AddSids interface. The application then provides the client context with the principal's distinguished name (DN) by using the LDAPQueryDN attribute on the AzClientContext2 object.

The steps to create an ADAM group and assign a user to are as follows:

  1. Create ADAM Group (using ADAM ADSI Edit, LDP.EXE, another tool, or code).
  2. Add ADAM principal to ADAM Group.
  3. In a custom Authorization Manager UI, create a Role Assignment and assign the ADAM user or group to a role (a custom UI is needed because the Windows Object Picker does not currently support ADAM).

For testing purposes you could use the LDP.exe tool to retrieve the ADAM user or group SID, and the Authorization Manager scriptable interfaces to assign the user or group to a role (such as the IAzRole::AddMember method).

The application that uses Authorization Manager performs the following steps (see the code sample below for details):

  1. Initialize the store and application (explained in the previous section).
  2. After a client connects and has been authenticated (typically via ldap_bind), create a client context using the IAzClientContext::InitializeClientContext2 interface.
  3. Query the user's objectSid attribute to obtain the user's SID, and add this to the empty client context via the IAzClientContext::AddStringSids method.
  4. Query the user's tokenGroups attribute which will contain the user group SIDs (see the sample code below). Add ADAM group SIDs to the client context object created above via the IAzClientContext2::AddSids method.
  5. Query the client's distinguished name in ADAM (see the sample code below.)
  6. Add principal DN to client context via IAzClientContext2::LdapQueryDN, which will support dynamic LDAP query groups

The second approach requires less code and provides less administration. In this approach you use the Authorization Manager LDAP query group to check the attributes of the user when AccessCheck is called within the application. Doing this means that you don't have to write code to query the token groups of the client, but you do still have to query the client distinguished name. Another benefit of this approach is that you don't have to write a custom UI, since all the role members are LDAP query groups and not ADAM principals. The drawback to this approach is that access checks will be slower than the in the first option because they will result in LDAP queries. Authorization Manager will cache group memberships for clients so, depending on your scenario, this option could work.

To set up ADAM and Authorization Manager for this approach:

  1. Create an ADAM principal.
  2. Configure the attributes of the ADAM principal; such as setting the title attribute, or give the ADAM principal membership in an ADAM group.
  3. In Authorization Manager, create an Authorization Manager LDAP Query Group, for example: (memberOf=<groupDN>) or (Title="Manager") To create a store in ADAM, see Using ADAM as a Store for Authorization Manager.

The application that uses Authorization Manager for application performs the following steps:

  1. Initializes the store and application (as explained in the previous section).
  2. When a client connects and is authenticated (typically via ldap_bind), creates a client context using the IAzClientContext::InitializeClientContext2 interface.
  3. Queries the client's distinguished name in ADAM (see the sample code below).
  4. Adds principal DN to client context via IAzClientContext2::LdapQueryDN, which will support dynamic LDAP query groups.

The following sample console application demonstrates the first approach above (SIDs and DN) to integrate ADAM principals with Authorization Manager.

using System;
using System.Collections;
using System.Collections.Generic;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using System.Text;
using Microsoft.Interop.Security.AzRoles;


namespace ADAMLogin
{
    class Program
    {
        static void Main(string[] args)
        {
            //AuthenticationTypes     AuthType = 
                  AuthenticationTypes.SecureSocketsLayer;
            AuthenticationTypes AuthType = AuthenticationTypes.None;
            string UserDN, UserSid;
            ArrayList TokenGroupSids;

            if (args.GetLength(0) < 7)
            {
                Console.WriteLine(
"usage:\n \"AdamLogin\" \"ServerName\" \"Partition\" \"UserDN\" +
"\"UserPassword\" \"AzManStoreURL\" \"AzManApplicationName\" \"OperationID\"");
                return;
            }
            try
            {
                LogonAdamUser(
                        args[0],
                        args[1],
                        AuthType,
                        args[2], args[3], out UserDN, out UserSid, out TokenGroupSids);

                Console.WriteLine("User Logged on Successfully:");
                Console.WriteLine("UserDN {0} , UserSid {1}", UserDN, UserSid);
                /*foreach (string GroupSid in TokenGroupSids)
                {
                    Console.WriteLine("GroupSid{0}", GroupSid);
                }*/

                // Load AzMan Store
                AzAuthorizationStoreClass AzStore = null;
                AzStore = new AzAuthorizationStoreClass();
                AzStore.Initialize(0, args[4], 0);
                Console.WriteLine("Opened Store:");

                IAzApplication2 AzApp = null;
                AzApp = (IAzApplication2)AzStore.OpenApplication(args[5], null);
                Console.WriteLine("Opened Application:" + AzApp.Name);

                //Create Empty ClientContext
                IAzClientContext2 ClientCon = null;
                ClientCon = (IAzClientContext2)AzApp.InitializeClientContext2("Adam user", null);
                //Add user Sid and group sids to client context
                object[] userSids = new Object[TokenGroupSids.Count + 1]; //Group sids + user sid
                
                // Add UserSid
                userSids[0] = (object)UserSid;

                //AddGroup Sids
                int i = 1;
                foreach (string GroupSid in TokenGroupSids)
                {
                    userSids[i] = (object)GroupSid;
                    i++;
                }

                ClientCon.AddStringSids(userSids);
                Console.WriteLine("Added Adam user sid and group sids to client context.");

                //Set LDAP QueryDN for adam user. This is needed if LDAP query groups are involved.
                ClientCon.LDAPQueryDN = "LDAP://" + args[0] + "/" + UserDN;
                Console.WriteLine("Set LDAPQueryDN on ClientContext:" + ClientCon.LDAPQueryDN);

                //Do AccessCheck
                object[] scope = new Object[1];
                scope[0] = (object)""; //Application Scope

                object[] operations = new Object[1];
                operations[0] = Int32.Parse(args[6]);

                object[] results;
                results = (object[])ClientCon.AccessCheck("Adam User AccessCheck", 
   (object)scope, (object)operations, null, null, null, null, null);

                foreach (int iRes in results)
                {
                    Console.WriteLine("*********************************");
                    if (iRes == 0)
                    {
                        Console.Out.WriteLine("ACCESS GRANTED");
                    }
                    else
                    {
                        Console.Out.WriteLine("ACCESS DENIED");
                    }
                    Console.WriteLine("*********************************");
                }
            }
            catch (Exception Ex)
            {
                Console.WriteLine(Ex.Message);
                Console.WriteLine(Ex.StackTrace);
            }
        }

        /****************************************************************
         * Purpose: Given the userPrincipalName of a user and 
         *           it's password, retrieve the user's distinguishedName,
         *           string sid, and tokenGroups.
         ****************************************************************/

        public static void LogonAdamUser(
                string                  adamServer,
                string                  partitionName,
                AuthenticationTypes     AuthType,
                string                  username, 
                string                  password, 
                out string              userDN, 
                out string              userSid, 
                out ArrayList           tokenGroupSids
            )
        {

            string adsPath = null;

            if ((Environment.OSVersion.Version.Major < 5)
                || ((Environment.OSVersion.Version.Major == 5)
                    && (Environment.OSVersion.Version.Minor <= 1)
                   )
               )
            {
                adsPath = "LDAP://" + adamServer;
            }
            else
            {
                adsPath = "LDAP://" + adamServer + "/RootDSE";
            }
            

            //Incase you don't have SSL setup, change the AuthenticationTypes to AuthenticationTypes.None.
            DirectoryEntry entry = new DirectoryEntry(adsPath, username, password, AuthType);

            entry.RefreshCache(new string[] { "tokenGroups" });
            PropertyValueCollection propertyValues = entry.Properties["tokenGroups"];
            Console.WriteLine("Token groups = {0}", propertyValues.Count);

            tokenGroupSids = new ArrayList();
            foreach (object val in propertyValues)
            {
                string stringSid = ConvertSidToStringSid((byte[])val);
                tokenGroupSids.Add(stringSid);
            }

            adsPath = "LDAP://" + adamServer + "/" + partitionName;
            entry.Path = adsPath;

            string filter = "(&(objectClass=user)(userPrincipalName=" + username + "))";
            string[] propertiesToLoad = new string[] { "objectSid", "distinguishedName" };
            DirectorySearcher searcher = new DirectorySearcher(entry, filter, 
   propertiesToLoad, SearchScope.Subtree);

            //  UPN has to be unique for authentication to work. 
            //  So assuming that only 1 entry will be returned.
            SearchResult result = searcher.FindOne();

            if ((result.Properties.Contains("distinguishedName")) && 
               (result.Properties["distinguishedName"].Count > 0))
                  userDN = 
result.Properties["distinguishedName"][0].ToString();
            else
                userDN = null;

            if ((result.Properties.Contains("objectSid")) && (result.Properties["objectSid"].Count > 0))
                userSid = ConvertSidToStringSid(((byte[])(result.Properties["objectSid"][0])));
            else
                userSid = null;
        }

        [DllImport("Advapi32.dll", EntryPoint = "ConvertSidToStringSidW", CharSet = CharSet.Unicode, 
   SetLastError = true)]
        public static extern int ConvertSidToStringSidW(IntPtr pSid, ref IntPtr stringSid);

        [DllImport("kernel32.dll", EntryPoint = "LocalFree")]
        public static extern int LocalFree(IntPtr mem);

        /**********************************************************
         * Purpose: To convert the sid in byte form to string form.
         **********************************************************/
        private static string ConvertSidToStringSid(byte[] sidBytes)
        {
            string stringSid = null;
            IntPtr ptr = (IntPtr)0;

            // Allocate memory for Byte[]
            IntPtr sidPtr = Marshal.AllocHGlobal(sidBytes.Length);
            // Copy byte[] to allocated memory
            Marshal.Copy(sidBytes, 0, sidPtr, sidBytes.Length);

            //Convert sid to string sid
            int result = ConvertSidToStringSidW(sidPtr, ref ptr);
            // Free allocated memory
            Marshal.FreeHGlobal(sidPtr);
            if (result == 0)
            {
                Console.WriteLine("ERROR converting sid to string sid: {0}", Marshal.GetLastWin32Error());
            }
            else
            {
                try
                {
                    stringSid = Marshal.PtrToStringUni(ptr);
                }
                finally
                {
                    LocalFree(ptr);
                }
            }
            return stringSid;
        }

    }
}

As stated above, using this technique will require a custom UI. See the Microsoft Platform SDK documentation and samples for guidance on using the Authorization Manager administrative interfaces to construct a UI. The following VBScript demonstrates what a command line script to add a user to a role would look like:

'Script to Add a SID to Role in AzMan Application
'Can be extended to add the SID to Role/Group in Store/Application/scope
'Usage AddSidToAzMan "StoreURL" "ApplicationName" "RoleName" "SID"
Dim AzSt 
Set AzSt = CreateObject("AzRoles.AzAuthorizationStore")
AzSt.Initialize 0, WScript.Arguments(0)

Dim AzApp
Set AzApp = AzSt.OpenApplication(WScript.Arguments(1))
WSCript.Echo "Opened Appplicaton:" & AzApp.Name

Dim AzRole
Set AzRole = AzApp.OpenRoleAssignment(WScript.Arguments(2))
Wscript.Echo "Opened RoleAssignment:" & AzRole.Name

AzRole.AddMember WScript.Arguments(3), 0
AzRole.Submit 0,0
WScript.Echo "Added SID:" & WScript.Arguments(3) & " to Role: " & WScript.Arguments(2)

For more information about how access control is determined in Active Directory and ADAM, see Controlling Access to Active Directory. More aspects will be covered in an upcoming paper of this series as well.

Active Directory Federation Services (ADFS)

With Windows 2003 SP1, Authorization Manager can work with ADFS to map claims to application roles. ADFS provides applications with a framework to authenticate federated users and provide a set of security claims about the user. The claims can be used in a federated application to identify and authorize the client's access to application resources. Using Authorization Manager and ADFS together, applications can leverage the federated authentication services of ADFS and the application authorization framework of Authorization Manager. This enables the application to leverage all the framework and integration of Authorization Manager and authorization to be designed and written to application operations independent of the authentication mechanism or user type (AD, ADAM, or Federated ADFS client.)

The primary step in integrating Authorization Manager with ADFS is to map the ADFS claims to Authorization Manager application groups or role assignments. This is done by the application at runtime. Once the ADFS authentication is successful and a WebSSO object is successfully created, the application loops on each claim in the claim context. By default, ADFS supports group claims. To map the group claims to AzMan roles or groups, applications use the following logic:

For each group in the ADFS claim context

   Add claim to AzMan client context via IAzCleintContext2.AddRoles

You may choose to use IAzClientContext2.AddGroups if you prefer the ADFS group claims be mapped to Authorization Manger groups. It is important that the Authorization Manager group or role assignment exists in the Authorization Store of the application before the mapping.

The following code snippet demonstrates the process of mapping ADFS claims to Authorization Manager roles:

// sample function in an AzMan helper class to build an empty context

public void GetAzContext()
{
_azApp = _azStore.OpenApplication2("Trey Ordering", null);
_azContext = _azApp.InitializeClientContext2("Trey Ordering", null);
}

ADFS populates the HttpContext.User property with a GenericPrincipal object with the ADFS groups as Roles and is therefore one option to load the AzMan security context.

// Pseudo code  
//create sample wrapper: class AzMan

// private String _azmanStoreLocation;
// private AzAuthorizationStoreClass _azStore;
// private IAzApplication2 _azApp;
// private IAzClientContext2 _azContext;

public void InitAz(String azmanStoreLocation)
{
_azmanStoreLocation = azmanStoreLocation;
_azStore = new AzAuthorizationStoreClass();
_azStore.Initialize(0, azmanStoreLocation, null);
_azStore.UpdateCache(null);
}

//These are the Roles that have been mapped via the ADFS MMC e.g. Their 
// SalesAdmin group would map to our OrderEntry role or an Order Entry 
// Group depending on the pivot-ability desired.  A claim to group mapping 
// could offer a great deal of flexibility.
// The pseudo code 
foreach (Claim claim in webSsoId.ClaimCollection) {             
claimUri = claim.Resource.Uri.ToString();
            claimValue = claim.Resource.Value.ToString();
            System.Diagnostics.Trace.WriteLine(claimUri + " - " + claimValue);
 
            if ("https://www.microsoft.com/security/XSI/Claims:group" == claimUri) {
               // Found a group claim
//Pseudo code or wrapper
             azMan.AddRoleToContext(claimValue);
//the call looks like 
(obj.AddRoles(ARole,null))
}

// In class sample wrapper class for AzMan called "azMan"
public Boolean AddRoleToContext(String roleName)
{         Boolean bResult = false;
         object[] roles = new object[1];
         roles[0] = roleName;   
   try
      {
      _azContext.AddRoles(roles, null);
      bResult = true;
      }
         catch (Exception)
         {
         }
   
         return (bResult);
      }

}

Using Claims with Authorization Manger

Active Directory Federation Services supports claims that go beyond the simple role and group claims in the scenario above. Authorization Manager has additional support for developer and deployers to integrate claims into an application.

The Authorization Manager BizRule can be used as mechanism if you require mapping arbitrary runtime claims into Authorization Manager roles. In Authorization Manager you can attach BizRules to tasks and role definitions or tasks. Using a BizRule a claim-like spending limit could be integrated to the authorization policy of a role, task, or group. To do this the application would have to send the spending limit claim value into the Authorization Manager AccessCheck call (see BizRules above) and a developer or deployer would have to provide a rule to check the spending limit parameter and act accordingly.

Using Authorization Manager with Custom Principals

Applications that use custom or non-Windows principals (for example, SQL, Passport, Microsoft Commerce Server, or ADAM principals) can leverage most of Authorization Manager's runtime features. (For information on integrating ADAM principals see the Using ADAM Principals in Authorization Manager.)

Whether in Active Directory or in XML, Authorization Manager stores SIDs as members of groups and roles, except for LDAP query groups, which don't have static members. When a SID is added to a role or an application group, it is not verified as a SID corresponding to an Active Directory or Windows NT account. You can add a completely meaningless or custom SID as a member of a role or a group.

When IAzClientContext::AccessCheck is called, the SIDs in the client context are compared to SIDs in Authorization Manager roles and groups and there is no validation of the SID as an Active Directory account. If a client context has a custom SID and a role has the same custom SID assigned to it — either directly or by way of an application group that has the same custom SID — the AccessCheck function will check if the SID is a member of a role or group with the requested operation and grant the permissions associated with the roles to which the SID is assigned. You can use this behavior to add custom principal types to roles or application basic groups in Authorization Manager by creating a custom SID for each custom principal and assigning that SID to appropriate roles and application basic groups.

Textual SIDs have the structure S-<Version>-<SID AuthorityID>-X-Y-Z, where Version is the SID version number, AuthorityID is the security authority ID of the issuer of the SID, and X, Y and Z are sub-authority values. (Currently, all SIDs are version 1.) SIDs are variable in length because the number of sub-authorities in SIDs can vary. SIDs can have up to SID_MAX_SUB_AUTHORITIES sub-authorities. (This is defined as 15 in winnt.h.) Custom SIDs use 1 for the version number, the SECURITY_RESOURCE_MANAGER_AUTHORITY for the AuthorityID value, and custom values for sub-authorities.

When creating custom SIDs you must establish a SID design for your application. For example, you might have S-1-9-AppInstanceGUID-UserGuid, where 9 is the resource manager sub-authority and AppInstanceGUID and UserGUID are each broken into four sub-authorities. You can also use S-1-9-AppInstanceGuid-UserRID, where UserRID is a unique number for the user in the scope of the application instance. If you want to use custom groups you can create a SID for each group using either of these techniques. For sample code that generates a GUID, see the Microsoft Windows Software Development Kit (SDK).

Once you have created a custom SID, you can add the SID to Authorization Manager roles or groups using the AddMember method of the IAzRole or IAzApplicationGroup interfaces.

To initialize a client context with a custom SID, you can call IAzApplication2::InitializeClientContext and specify a custom SID in string form. This creates a valid client context that is suitable for performing access validation for the custom principal. In practice, after a custom principal authenticates, an application maps the authenticated principal to a custom SID that is previously created for the principal and calls InitializeClientContextFromSID to attain an Authorization Manager client context. When AccessCheck is called on this client context, this SID will be evaluated for membership in Authorization Manager roles and groups.

Drawbacks to custom principals

There are some drawbacks to using custom principals.

  • You cannot use LDAP query groups. If the user is not in Active Directory or ADAM, LDAP query groups are not supported. When using ADAM principals you can use LDAP query groups (see Using ADAM Principals in Authorization Manager).
  • You cannot use the Authorization Manager MMC user interface. Since the user interface validates user SIDs when displaying role or group memberships, administrators see textual string SIDs in the user interface and not usernames. More importantly, the user interface only obtains SIDs to enter into roles and application groups by way of Object Picker, which only allows you to pick valid accounts or groups.

Steps for Using Custom Principals with Authorization Manager

  1. Authenticate the custom principal user and then map the custom principal to a custom SID in your user account store. Typically a custom SID is stored as an additional attribute on a user or group record.
  2. If you are also using custom groups, query the custom group SIDs from your account store.
  3. Use the IAzApplication2::InitializeClientContext method to create an empty client context. Add the user SID and the group SIDs to the client context using the IAzClientContext2::AddSids method.
  4. When the client (represented by the custom client context) makes a request, you can use AccessCheck to verify that the custom client context is authorized to perform the specified operations in the specified scope as you would with a Windows principal.

The following figure illustrates the process of authorizing custom principals.

Figure 29. Authorizing custom principals

Deployment Considerations for Developers

This section introduces a few additional deployment considerations for developers using Authorization Manager.

Authorization Manager Availability

Windows Authorization Manager is a component included with Microsoft Windows Server 2003 Service Pack 1. Authorization Manager runtime is also available via download for Windows 2000 Server (runtime only) and for Windows XP via the Windows Server 2003 Administration Pack (links below).

For development in managed code, you should use the provided Primary Interop Assembly (PIA) Microsoft.Interop.Security.AzRoles.dll, which provides managed interfaces for the COM-based azroles.dll, to access the Windows Authorization Manager object model. To run Windows Authorization Manager, the actual COM object that Microsoft.Interop.Security.AzRoles.dll wraps must also be registered on the computer. This COM object is installed and registered automatically with Windows 2003 Server and should not typically be installed manually.

The download locations for Windows XP and Windows 2000 are as follows:

Developers and deployers on Windows 2000 and XP will have to register the PIA into the Global Assembly Cache (GAC). The Authorization Manager PIA is available for Windows 2000 and XP in the Authorization Manager Windows 2000 Runtime. .NET Framework Assemblies (including the Authorization Manager PIA) may be added to the GAC by using the gacutil.exe utility found in the .Net Framework SDK. You should use the latest version of the PIA unless the application requires an earlier version. The PIA is located on Windows 2003 systems with Service Pack 1 in the following folder: %windir%\Microsoft.Net\Authman\(version). Additionally, versions 1.0 and 1.2 of the PIA are available in a download of the Windows 2000 back-port.

For Windows Server 2003 Service Pack 1 and Windows 2000 Authorization Manager Runtime version 1.1, the version information of the PIA is:

Microsoft.Interop.Security.AzRoles, Version=1.2.0.0

For Windows Server 2003 gold (original release) and the Windows 2000 Authorization Runtime version 1.0 the version information of the PIA is:

Microsoft.Interop.Security.AzRoles, Version=1.0.0.0

Note   You should not use this version unless an existing application requires it.

To register the Authorization Manager PIA in the GAC, use the following command line:

GacUtil.exe –I Microsoft.Interop.Security.AzRoles.dll

Deployment Approaches

The developer should be aware of the infrastructure supporting the application and the implications of a given authorization store selection. At this time, scripting store structures tends to be the most flexible approach to move between store types. XML offers a lot of simplicity as it may be moved between environments easily, but if moving between XML and Active Directory or ADAM using the script approach is preferred.

One of the deliverables in the development phase may be a setup program that installs the sample intranet and extranet applications on your computers. This section highlights the techniques used in the custom installers of the sample applications. Information is provided about how to use the custom installers to programmatically configure directory security for the virtual directories, as well as programmatically configure Windows Authorization Manager stores, applications, roles, tasks, and operations.

  • A necessary step for all installations using Windows 2003 SP 1 update Authorization Manager is that the Authorization Manager PIA (Primary Interop Assembly) is installed to the GAC (Global Assembly Cache).

For more information about setup steps, custom installers, and so on, see:

Application Installation

The Authorization Manager policy store, application, operations, and tasks need to be created as part of an installation program. Once you have decided which default operations, tasks, and roles are needed for your customized solution you are ready to build an installer for your application that will configure Windows Authorization Manager.

When an application is installed, it can install authorization policy information into a new or existing authorization store by calling the Authorization Manager API. If the application supports installing its authorization policy into an existing store, the installation process must give the installing user a way to specify the location of the existing store. After initializing the authorization policy store, the install application uses the CreateApplication method to create an application object in the policy store. Then, for each operation, the installation application calls the CreateOperation method. Initial tasks, scopes, and roles are installed using the CreateTask, CreateScope, and CreateRole methods, respectively. The following VBScript creates a store and installs the authorization policy for a simple expense application.

Make sure to use the new interfaces with SP1 for performance enhancements.

'--- Initialize the admin manager object 
Dim pAzManStore 
Set pAzManStore = CreateObject("AzRoles.AzAuthorizationStore2") 
'--- Create a new store for expense app 
' 0 = Open store for Access Checking. 
' AZ_AZSTORE_FLAG_CREATE   = 0x1, 
' AZ_AZSTORE_FLAG_MANAGE_STORE_ONLY = 0x2, // cannot do access 
checking. 
' AZ_AZSTORE_FLAG_BATCH_UPDATE      = 0x4, 
pAzManStore.Initialize 1+2+4, "msxml://C:\AzPolicy.xml" 
pAzManStore.Submit 
Dim App1 
Set App1 = pAzManStore.CreateApplication("Expense") 
App1.Submit 
'--- Create operations ----------------------- 
Dim Op1 
Set Op1=App1.CreateOperation("RetrieveForm") 
Op1.OperationID = CLng(61) 
Op1.Submit 
Set Op1=App1.CreateOperation("EnqueRequest") 
Op1.OperationID = CLng(62) 
Op1.Submit 
Set Op1=App1.CreateOperation("DequeRequest") 
Op1.OperationID = CLng(63) 
Op1.Submit 
Set Op1=App1.CreateOperation("UseFormCotnrol") 
Op1.OperationID = CLng(64) 
Op1.Submit 
Set Op1=App1.CreateOperation("MarkFormApproved") 
Op1.OperationID = CLng(65) 
Op1.Submit 
Set Op1=App1.CreateOperation("SendApprovalNotify") 
Op1.OperationID = CLng(66) 
Op1.Submit 
'--- Create tasks ------------------------------ 
Dim Task1 
Set Task1=App1.CreateTask("Submit Expense") 
Task1.AddOperation CStr("RetrieveForm") 
Task1.AddOperation CStr("EnqueRequest") 
Task1.AddOperation CStr("UseFormCotnrol") 
Task1.Submit 
Set Task2 = App1.CreateTask("Approve Expense") 
Task2.AddOperation CStr("MarkFormApproved") 
Task2.AddOperation CStr("SendApprovalNotify") 
Task2.AddOperation CStr("DequeRequest") 
Task2.BizRuleLanguage = CStr("VBScript") 
Task2.BizRule = "Dim Amount" & vbnewline  & _ 
           "AzBizRuleContext.BusinessRuleResult= FALSE" & vbnewline &_ 
           "Amount = AzBizRuleContext.GetParameter( " & Chr(34) &_ 
                              "Amount" & Chr(34) & ")"  & vbNewLine &_ 
           "if Amount < 500 then AzBizRuleContext.BusinessRuleResult= 
TRUE" 
Task2.Submit 
'--- Create role definitions ------------------------------ 
Set Task3 = App1.CreateTask("Expense Admin") 
Task3.AddTask CStr("Approve Expense") 
Task3.AddTask CStr("Submit Expense") 
Task3.IsRoleDefinition = TRUE 
Task3.Submit 
Set Task4 = App1.CreateTask("Expense User") 
Task4.AddTask CStr("Submit Expense") 
Task4.IsRoleDefinition = TRUE 
Task4.Submit 
'--- Create initial scopes and roles ------------------------------ 
'--- only one scope in this app (we may instead choose to use no scope) 
Dim Scope1 
Set Scope1 = App1.CreateScope("AllRoutines") 
Scope1.Submit 
Set RoleA=Scope1.CreateRole("Expense Administrator") 
RoleA.AddTask("Expense Admin") 
RoleA.Submit 
Set RoleB=Scope1.CreateRole("Expense User") 
RoleB.AddTask("Expense User") 
RoleB.Submit 
'--- Demo - add everyone to ExpenseUser Role -------------------------- 
RoleB.AddMemberName("Everyone") 
RoleB.Submit
 

Active Directory Storage

In addition to authentication, Active Directory can act as a repository for the Authorization Manager authorization store, which allows applications to take advantage of Active Directory replication and to leverage Authorization Manager locally from any location where there is a domain controller. However, it should be noted that Authorization Manager is domain-specific. The Authorization Manager store is contained within a single domain and it cannot be stored in an application partition. Therefore, if you want to use Authorization Manager in a multi-domain environment, you need to ensure that data in the Authorization Manager store is available to application service accounts that may be in other domains. This may be done by specifying the appropriate groups; however, some customers have chosen another approach.

Extended Store Replication Scenarios

By default, Active Directory and ADAM replicates Authorization Manager stores between instances, however, there are some cases where going beyond this is desired. In these situations some customers have utilized custom code to synchronize the Authorization Manager authorization store from a staging domain to multiple target domains. In this scenario, the target domains can be in any forest, and no trust relationships are necessary, provided you have an account with sufficient rights in the target directory. Writing such a tool will require use of the Authorization Manager administrative interfaces. See the latest version of the Windows SDK for documentation and samples (specifically the AzMigrate sample) on using these interfaces.

Domain mode requirements

The Authorization Manager Active Directory Store requires that the domain be in Windows Server 2003 functional level.

Application Partition

Currently Authorization Manager policy storage is not supported in an Active Directory Application Partition because the group object schema declares the SID attribute as a mustContain attribute. Currently the SID attribute cannot be created for an object created in an application partition. As a result, Authorization Manager application group objects cannot be added in an application partition.

Using ADAM as a Store for Authorization Manager Policy

You can use ADAM as a store for Authorization Manager policy. The ADAM installation process allows the installation of the Authorization Manager schema. This schema must be installed prior to creating an Authorization Manager policy store in ADAM. Policy can be managed in ADAM using the Authorization Manager MMC UI, through the Authorization Manager scriptable COM interfaces, or through a custom UI.

To use ADAM as a store for Authorization Manager policy:

  1. Make sure the Authorization Manager schema has been installed. This option is available in the ADAM instance creation wizard or via the ldifde.exe ADAM utility with the following command line:

    Ldifde.exe -i -f ms-azman.ldf -c "cn=configuration,dc=..." "#configurationNamingContext" -s ServerName:Port

  2. Assign the account the application accessing the Authorization Manager store will run as the Reader role for the ADAM instance.

  3. Make sure the container in which you want the store to be created exists prior to creating the Authorization Manager store. If you want to create a new container for the Authorization Manager store, you can create a container in ADAM via the ldp.exe tool.

  4. Use the Authorization Manager MMC Snap-in or the IAzAuthorizationStore::Initialize method to create the store as described in the corresponding section below.

  5. Assign application Administrators and Readers as appropriate to the Authorization Manager administrative roles AND the ADAM instance Reader role (see Setting ADAM Permissionsbelow).

Creating an Authorization Manager Policy Store in ADAM via IAzAuthorizationStore::Initialize method

After the Authorization Manager schema has been installed, you can create an Authorization Manager policy store through the Authorization Manager scriptable COM interface: IAzAuthorizationStore::Initialize method. To do so, you must specify (in the bstrPolicyURL parameter) the LDAP distinguished name (DN) of the store object to be created using the msldap provider type identifier, the ADAM server name, and LDAP port:

Msldap://servername:<port number>/cn=< name of store to be created >,CN=<existing container name>,...

The container in ADAM in which the store is to be created must exist prior to calling the IAzAuthorizationStore::Initialize method to create the Authorization Manager store.

Using the Authorization Manager UI to Create or Manage an Authorization Manager Policy Store in ADAM

After the Authorization Manager schema has been installed, you can use the Authorization Manager MMC snap-in UI to create or manage an Authorization Manager Policy store. To create or manage an Authorization Manager policy store using the Authorization Manager MMC snap-in UI, right-click the root Authorization Manager node in the left pane tree view and then select the New Authorization Store or Open Authorization Store menu options respectively. Select Active Directory as the store type, specify the LDAP distinguished name (DN) of the store object to be created or managed, and then specify the ADAM server name and LDAP port:

servername:<port number>/cn=< name of store to be created >,CN=<existing container name>,...

The container in ADAM in which the store is to be created must exist prior to attempting to create the Authorization Manager store.

When opening an existing authorization store, you have the option of launching the Authorization Manager MMC snap-in UI from the command line and specifying the distinguished name of the store as a command line argument (you can also create a shortcut with the following command line without the start command):

Start azman.msc "msldap://servername:<port number>/cn=< name of store to be created >,CN=<existing container name>,...

Setting ADAM Permissions

Permissions in ADAM need to be set properly in order to provide access to the calling process that will access the Authorization Manager store in this directory. The ADAM authorization model mirrors that of Active Directory; permissions on ADAM directory objects are assigned to users and groups using access control lists (ACLs). An ACL contains a list of users and groups that have been assigned permissions on a directory object, and it also contains the types of permissions (read, write, and so on) that they have been assigned. Each individual entry in an ACL is called an access control entry (ACE). If an ACL on a directory object does not explicitly assign permissions to a user, or to any groups that a user is a member of, the user will be denied access to that object. To use Authorization Manager as a policy store the policy administrators (the users who will manage the policy) and applications that will use the policy for authorization must have appropriate access on the Authorization Manager policy store object and all child objects (for example, applications, scopes, and roles).

To simplify configuring the ACLs on Authorization Manager policy in Active Directory and ADAM, Authorization Manager provides administrative roles that are configurable in the Authorization Manager MMC snap-in UI or by using the Authorization Manager scriptable programming interfaces. Authorization Manager currently supports two administrative roles, Administrator and Reader. Assigning a user or application account to these roles in Authorization Manager will cause the ACLs to act appropriately on Authorization Manager policy objects. Application policy administrators, who require read and write access to the policy, must be assigned to the Administrator role at the appropriate level in the store (store, application, or scope.) Applications using the Authorization Manager store for authorization typically should not have write access to the authorization policy and should be assigned the Reader role at the appropriate level in the policy store.

To assign users and application accounts to the Authorization Manager administrative roles, use the Authorization Manager MMC snap-in UI. This can also be done programmatically via the IAzAuthorizationStore::PolicyReader, IAzAuthorizationStore::PolicyAdministrator interfaces.

Important   When you assign users to either the Administrator or Reader Authorization Manager administrative roles at the application or scope level you must also assign them to the Delegated Users role at the store and application levels. This will allow those principals to read the objects at the store and application levels (such as application groups) that can be used in definitions and membership assignments at the lower application and scope levels.

ADAM maintains an additional layer of authorization. Each ADAM application partition has a Reader role that controls which principals can access the partition. Users and application accounts must be assigned to this role in order to gain access to the resources in the ADAM partition. All application policy administrators and application accounts that require read or write access to the Authorization Manager policy store must be in the Reader role for the ADAM instance that contains the store.

Authorization Manager Transactional and Concurrence Situation

Authorization Manager does not support transactions at this time; however, to support transactions a wrapper service could be written that would synchronize access and internally compensate for aborts. Authorization Manager does support multiple concurrent administrators in certain scenarios.

Authorization Manager in does not support concurrency when policy is stored in XML.

Authorization Manager does leverage Active Directory for support for concurrency. Active Directory and ADAM have a non-transactional model that supports concurrent additions and subtraction of multi-valued or linked object attributes. In Active Directory and ADAM, changes to object attributes are atomic (at the attribute level) so you never have an attribute that is a mesh of two changes. Active Directory uses a "last writer wins" mechanism to determine which write request will persist. Attributes are never merged; one write request (the last write received) will always win. For AD linked attributes (such as Authorization Manager role and group memberships, or links between operations, tasks, and role definitions) changes are additive; so concurrently adding or subtracting users or linking and unlinking operations, tasks, and roles is supported. However, the Authorization Manager MMC snap-in UI maintains a client-side cache that is not updated when the store is changed from some other UI or application; therefore, applications that require multiple concurrent administrators require a custom UI.

Multiple Administrators

A common solution for supporting multiple administrators is to create a custom user interface that allows for adding members to roles and whatever other functionality is required to provide the most common management functions. You can then secure the authorization store so write access to it is only possible from the management Web application and a small number of users responsible for more advance administrative operations that may require the Authorization Manager MMC snap-in UI. This approach allows for a single management server to control order or synchronize access to the management functions.

Delegation of Administration

When using the Active Directory Authorization Manager policy store, Authorization Manager supports delegation of administration at the authorization store, application, and scope levels. This allows higher-level administrators to give limited access to others to manage some subset of the authorization policy. The store, application, and scope objects can each have a set of Administrators and Readers. Administrators of a store, application, or scope can perform all operations that are needed to manage the authorization policy within the delegated scope, and Readers can only read the authorization policy. Additionally, users who have been delegated authority of a scope or application must be added to the Delegated User administrative role of the store. This allows the delegated user to view groups that are created at both the application and the store level.

When a scope has been delegated, BizRules cannot be added to tasks or role definitions that were created in the scope. Delegated scopes can use BizRules that are attached to tasks and role definitions at the application level. Store-level administrators can also globally prevent all BizRules from running by setting the ScriptEngineTimeout store level value to 0. After doing this, AccessCheck assumes that existing BizRules will return as false and access will not be granted through task or role definitions containing a BizRule.

Delegated Users allows scope administrators to view application and store level groups, and application level administrators to view store level groups. These users must be assigned by an application or an administrator as there is no automatic assignment by Authorization Manager.

Troubleshooting the ASP.NET Authorization Manger Store provider

Figure 30. Access Denied

ASP.NET needs permission to the store "Local Error". The store resides outside the Web site. Also, note that attempting to create a role that already exists will cause an error. Check for role existence before creation. The XML store does not support concurrent administration.

Troubleshooting ADAM Access with Active Directory

Use ADAM ADSI Edit, bind to well-known "configuration," and make sure that the service account has access (try adding to ADAM administrators). Depending on your configuration, you may need to add the aspnet/network service/computer account/* via the member attribute to ADAM to allow access.

Figure 31. ADAM policy store

The selection above is the policy store location seen in ADSI Edit.

Figure 32. ADAM container

The connection above is to the container of the policy store.

Figure 33 – ADAM Readers role

When you select Roles you will see the above in the right-hand pane.

Figure 34. Reader properties

Double-click or select member, and click Edit to bring up the following dialog box.

Figure 35. ADAM Users role

Click Add Windows Account and add the process account that needs read access to the store. You would need to add the process owner accessing ADAM to the administrator's role of ADAM to write to the store. After that, the store permission settings may be performed via the Authorization Manager MMC Snap-in (AzMan.msc) from the Actions menu – Reader, Administrator, or Delegated.

Conclusion

The role-based Authorization Manager Framework in the Windows Server 2003 family provides your applications with a powerful model for authorization management. The intuitive role-based authorization management model gives application administrators new capabilities to specify how they want to grant users access to resources. It gives them flexibility without the cost of custom authorization solutions or the complexity involved in existing models.

When your applications leverage the Authorization Manager framework, you can easily incorporate a powerful role-based authorization and administration model that can be integrated through simple COM interfaces or managed code. You can create custom Web-based management interfaces or use a provided Microsoft Management Console snap-in. By using Authorization Manager in your applications you can further leverage the Windows Server 2003 platform through Active Directory storage and identity management, integrated auditing, and flexible application groups.

Additional Resources

Code Samples

Additional Authorization Manager code samples are provided in the Windows SDK. Search on Microsoft.com for information about downloading or obtaining the Windows SDK. Samples are added or updated with each SDK release.

Authorization Manager Documentation

For a conceptual overview of Authorization Manager and information on Authorization Manager administration and operations see Role-Based Access Control for Multi-tier Applications Using Authorization Manager.

There are a number of supporting documents for various Authorization Manager Activities on MSDN such as the following:

Topic Description
Creating an Authorization Policy Store Object in Script An authorization policy store contains information about the security policy of an application or group of applications.
Creating an Application Object in Script For each application that uses an authorization policy store, you must create an IAzApplication object and save it to a policy store.
Defining Operations in Script An operation is a low-level function or method of an application.
Grouping Operations into Tasks in Script A task is a high-level action that users of an application need to complete. Tasks consist of operations, which are low-level functions and methods of the application.
Grouping Tasks into Roles in Script A role represents a category of users and the tasks those users are authorized to perform.
Defining Groups of Users in Script An IAzApplicationGroup object represents a group of users. Roles can then be assigned to this group of users collectively. An IAzApplicationGroup object can also include other IAzApplicationGroup objects as members.
Adding Users to an Application Group in Script An IAzApplicationGroup object represents a group of users. Roles can then be assigned to this group of users collectively. An IAzApplicationGroup object can also include other IAzApplicationGroup objects as members.
Supporting Tasks for Authorization in Script Topics demonstrating the use of the Authorization Manager scripting interfaces.

Authorization Manager Web Log

See the Authorization Manager Blog (https://blogs.msdn.com/azman) for new announcements and frequently asked questions.