Walkthrough: Develop an Impersonation Web Application

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

When you impersonate a user in an application for Microsoft Office Project Server 2007, you adopt the global and category permissions of the impersonated user. This article shows how to develop, install, configure, and debug a Web application for Project Server that uses impersonation. The Web application allows you to log on Project Server using Windows or Forms authentication, checks your permissions for listing and creating projects, and then lets you impersonate any other Project Server user to list and create projects.

The Microsoft Office Project 2007 SDK download includes the complete Impersonation Web application source code. For a link to the download, see Welcome to the Microsoft Office Project 2007 SDK.

Important noteImportant

The Impersonation Web application is an example only for demonstration purposes. The application is designed to run on a test installation of Project Server. It allows anyone with a Project Server account to log on, impersonate any other user, and create projects. To use any impersonation application on a production server, you must programmatically limit usage and add security checks that are appropriate for your organization.

Impersonation Applications

Impersonation is useful in client applications for end-users who need to do specific jobs with elevated privileges. For example, you can use impersonation to create or update timesheets programmatically. Impersonation is also commonly used in middle-tier components that integrate Project Server with line-of-business (LOB) systems. For example, a middle-tier component can run with the administrator permission set, or with a custom permission set of a specified Forms authentication user, to synchronize data between Project Server and a human resources application.

Most applications for Project, including Microsoft Office Project Professional 2007 and Project Web Access, call the Project Server Interface (PSI) Web services through the Project Web Access URL. Project Server enforces the security permissions of your account when you log on through an application that uses the Project Web Access URL. Impersonation requires direct calls to the PSI through the Shared Services Provider (SSP) Web application that hosts Project Web Access.

An impersonation Web application is based on the same code that the sample console application and the ProjTool test application both use. Impersonation applications use proxy classes for the PSI Web services. You can derive classes from the proxies that override the GetWebRequest method to add Web request headers, and provide a method to set the impersonation context for each proxy object.

Web applications have many requirements, and there are numerous sources of information about how to develop them. An impersonation Web application for Project Server has additional requirements. For example, you must do the following:

  • Create a top-level Web site that is not a Microsoft Windows SharePoint Services site.

  • Use a service account that Project Server trusts for the application pool of the site.

  • Add Windows authentication to the Microsoft Internet Information Services (IIS) metabase data for the site.

  • Add the same machineKey element to the web.config file for the site that Project Web Access uses for encryption, decryption, and validation of Forms authentication cookies and ViewState data.

  • Maintain the state of the URLs for calling the PSI, as well as the PSI proxy objects, throughout the user session.

  • Typically, page through datasets, rather than attach a GridView control to a database table or a DataView object.

This walkthrough includes the following procedures for creating an impersonation Web application:

  1. Creating a Web Site for an Impersonation Application

    1. Configuring a Web Site to use a Different Farm (alternate procedure using a trusted account)

    2. Account Security and 401 Errors

  2. Installing and Configuring the Sample Web Application

  3. Creating the PSI Proxy and Derived Classes

  4. Maintaining State for PSI Calls and Other Objects

  5. Using the GridView Control with a DataSet

  6. Debugging the Web Application

The sample application includes other features that this walkthrough does not explain, such as a master page with a TreeView menu, a theme and skin, a style sheet (CSS file), and common ASP.NET 2.0 Web server controls. The sample application does not use ASP.NET AJAX, although you certainly could add it.

For more information about the console application, see How to: Write a Simple Impersonation Application. For a sample Windows client application that uses impersonation, see Using the ProjTool Test Application.

Using the Sample Web Application

The Impersonate page in the sample Web application (Figure 1) shows the identity of the application user. The application user can log on Project Server using Windows or Forms authentication. In the figure, the user has clicked Forms for the authentication type and logged on as a Project Server user named Joe. Joe has the NewProject global permission to create a project, but is denied the ManageQueue permission needed to execute the ReadProjectList PSI method.

If you (as the application user) selected Joe in the Select User drop-down list and then clicked Impersonate, you would run the application with Joe's permissions. If you then clicked List Draft Projects, the application would return an exception because Project Server does not allow Joe to use ReadProjectList. If you selected the Use ReadProjectStatus() check box while impersonating Joe, the application would call the ReadProjectStatus method instead, and Joe could get the list of draft projects.

Figure 1. Impersonating a user with different permissions

Impersonating a user with different permissions

In Figure 1, however, the logged-on user Joe is impersonating the Administrator user, who does have the ManageQueue permission. Therefore, Joe can use the ReadProjectList method even though his own account does not have permission to do so. The application also enables an impersonated user (who has the NewProject permission) to create and publish a project, and then refreshes the Draft Projects grid to show the new project. The Draft Projects grid shows up to six projects and dynamically creates additional grid pages as needed.

Creating a Web Site for an Impersonation Application

A Web site that will host an application for impersonation of Project Server users must satisfy the following conditions:

  • The Web site must be a top-level site.

  • The site must use the same version of ASP.NET that Project Web Access uses.

  • The site cannot be a SharePoint site. It must allow access to scripts and installation of executable files.

  • The site must run under a service account that Project Server trusts.

  • The IIS metabase must specify the NTLM authentication provider for the site.

The sample application in the download is designed to be installed on a computer that hosts Project Web Access. Procedure 1 shows how to create a Web site for the sample. If you modify the application and configuration procedures, you can also create an impersonation Web site on a server in a different farm. Procedure 1a shows the modified steps for creating an impersonation Web site in a different farm.

NoteNote

The sample Web application runs properly only when you install it on the same computer that hosts Project Web Access.

In Procedure 1, the site uses the application pool identity that Project Web Access uses. Procedure 1a shows how to use a different identity on another Windows SharePoint Services farm.

Procedure 1. To create a Web site to host an impersonation application:

  1. On a test Project Server computer, create a directory for the source files, for example, C:\Project\Impersonation.

  2. Using IIS Manager, create a top-level Web site named, for example, Impersonation.

  3. Use the local path you created in Step 1 (C:\Project\Impersonation). Allow access to scripts access and installation of executable files.

  4. Disable anonymous access (use Integrated Windows Auth only).

  5. Set the port to a value different from the ports that Project Web Access and Windows SharePoint Services use on that computer; for example, set the port to 5636. Project Web Access typically uses port 80 for Windows authentication and port 81 for Forms authentication. Your Impersonation site URL would therefore be http://ServerName:5636.

  6. Create a new application pool in IIS Manager, for example, ImpersonationAppPool. On the Identity tab in the ImpersonationAppPool Properties dialog box, set the Configurable property of the Application Pool Identity to the same user account and password for the Project Web Access site administrator. To find the user account for configuring Project Web Access, do the following:

    1. Open the SharePoint 3.0 Central Administration site, and then click Application Management.

    2. On the Application Management page, click Create or configure this farm's shared services.

    3. Click the name of the SSP where Project Web Access is installed. For example, click SharedServices1 (Default).

    4. On the home page of the Shared Services Administration site, click Project Web Access Sites.

    5. On the Manage Project Web Access Sites page, pause the mouse pointer over the site instance you want, click the down-arrow, and then click Edit.

    6. On the Edit Project Web Access Site page, use the value in the Administrator Account box. For example, set the Application Pool Identity to domain\pwaAdminName.

  7. In IIS Manager, do the following:

    1. Right-click the Impersonation Web site, click Properties, and then click the Home Directory tab. Set the Application pool value to the pool you created in Step 6 (ImpersonationAppPool).

    2. On the Impersonation Web site Properties page, click the ASP.NET tab, and then set the ASP.NET version to 2.0.50727.

    3. Right-click the local computer name, and then click Properties. Click Enable Direct Metabase Edit.

  8. Use a text editor such as Notepad to open the metabase.xml file in %systemroot%\system32\inetsrv. Search for the site name within an IISWebServer tag. Add the attribute NTAuthenticationProviders="NTLM". For example, following is the complete element for a new Impersonation site.

    <IIsWebServer     Location ="/LM/W3SVC/784768436"
                AuthFlags="0"
                NTAuthenticationProviders="NTLM"
                ServerAutoStart="TRUE"
                ServerBindings=":5636:"
                ServerComment="Impersonation">
    </IIsWebServer>
    
  9. Restart IIS.

Configuring a Web Site to use a Different Farm

Procedure 1a shows an alternate list of steps to create and configure an impersonation Web site on a Windows SharePoint Services farm that is separate from the farm with Project Web Access. In the alternate procedure, both the farms are within the same domain.

NoteNote

The sample Web application works correctly only when you create a Web site using Procedure 1. To use Procedure 1a, you must modify the sample Web application so that it does not depend on the Windows credentials of the application user. For example, the sample application uses the account name of the application user from HttpContext.Current.User.Identity.Name to set the initial value of the drop-down list of Project Server users. You would need to remove dependencies on the account name.

You can run an impersonation Web application on a separate farm if the SSP for the PSI Web services trusts the application pool identity of the Web application. For example, if Project Server is installed on two farms and the SSP administration account is the same for both farms, you can develop an impersonation Web application that talks to both Project Server farms.

Procedure 1a (alternate). To create an impersonation Web site in a different farm:

  1. On a test server, create a directory for the source files, for example, C:\Impersonation.

  2. Using IIS Manager, create a top-level Web site named, for example, Impersonation.

  3. Use the local path you created in Step 1 (C:\Impersonation). Allow access to scripts access and installation of executable files.

  4. Disable anonymous access (use Integrated Windows Auth only).

  5. Set the port to a value different from the ports that other Windows SharePoint Services sites use on that computer; for example, set the port to 5636. Your Impersonation site URL would therefore be http://ServerName:5636.

  6. Create a domain account that is to be used only for a trusted account on the Shared Service Provider of the remote Project Web Access computer. Add the account to the Administrators group on the local computer. For example, create an account named ManageImpersonation.

  7. Create a new application pool in IIS Manager, for example, ImpersonationAppPool. On the Identity tab in the ImpersonationAppPool Properties dialog box, set the Configurable property of the Application Pool Identity to the user account and password you created in Step 6.

  8. In IIS Manager, do the following:

    1. Right-click the Impersonation Web site, click Properties, and then click the Home Directory tab. Set the Application pool value to the pool you created in Step 7 (ImpersonationAppPool).

    2. On the Impersonation Web site Properties page, click the ASP.NET tab, and then set the ASP.NET version to 2.0.50727.

    3. Right-click the local computer name, and then click Properties. Click Enable Direct Metabase Edit.

  9. Use a text editor such as Notepad to open the metabase.xml file in %systemroot%\system32\inetsrv. Search for the site name within an IISWebServer tag. Add the attribute NTAuthenticationProviders="NTLM". For example, following is the complete element for a new Impersonation site.

    <IIsWebServer     Location ="/LM/W3SVC/784768436"
                AuthFlags="0"
                NTAuthenticationProviders="NTLM"
                ServerAutoStart="TRUE"
                ServerBindings=":5636:"
                ServerComment="Impersonation">
    </IIsWebServer>
    
  10. Restart IIS.

  11. To have Project Server trust the Impersonation Web site, the SSP administrator for the remote Project Web Access computer must do the following:

    1. On the Project Web Access computer, open the Windows SharePoint Services 3.0 Central Administration page, and then click the Application Management tab.

    2. Click Grant or configure shared services between farms, in the Office SharePoint Server Shared Services section of the Application Management page.

    3. On the Manage Shared Services between Farms page, click This farm will provide shared services to other farms.

    4. In the SSP Name list, select the name of the SSP where the Project Web Access instance is located.

    5. Add the trusted account name you created in Step 6. For example, add domain\ManageImpersonation, and then click OK.

    6. On the Success page, copy the name of the parent farm's database server and the configuration database name to use when you configure the other farm to consume shared services.

  12. On the SharePoint farm where the Impersonation Web site is located, the SSP administrator must do the following:

    1. Open the Manage Shared Services between Farms page, and then click This farm will consume shared services from another farm.

    2. Type the values from Step 11.f into the Database Server and Database Name fields and set the authentication values. If Microsoft Office SharePoint Server is installed, Excel Services must remain associated with the local SSP. Click OK.

    3. On the Change Association between Web Applications and SSPs page, select the SSP you want to use for applications on the farm. You can leave existing applications associated with the local (default) SSP.

Account Security and 401 Errors

The sample Web application is designed to run where the Impersonation Web site application pool identity is the same user account for configuring Project Web Access. If you modify the sample application for the alternate procedure (Procedure 1a) to share access to the Web application across SSPs or with another Windows SharePoint Services farm, the account you use for the application pool identity should be a unique account. No one else should be able to use the account for shared access; otherwise, the Web application would not be secure because other users could have full access to Project Server through applications that use the direct PSI Web services.

If the Web application user gets the error message, "The request failed with HTTP status 401, Access Denied," the user's credentials are being rejected somewhere in the chain of communication between the browser and the SSP. If you follow the configuration steps in this article and still get a 401 error, the problem may be that you need to add the user to the SSP which hosts Project Web Access, or the SSP is not configured to process the user's account. Try either or both of the steps in Procedure 1b.

Procedure 1b. To add a user and configure the SSP for processing user accounts:

  1. Add the user or group to the SSP site. For example, to enable a user to view and modify Web pages in the SSP sites, do the following:

    1. Open the SharePoint 3.0 Central Administration page. Under Shared Services Administration in the Quick Launch, click the name of the SSP site that hosts Project Web Access.

    2. On the Shared Services Administration page, click the Site Actions drop-down menu, and then click Site Settings.

    3. On the Site Settings page, click People and groups in the Users and Permissions section .

    4. Add a user. On the Add Users page, click Give users permission directly and then select the Contribute check box.

  2. To enable the SSP to process accounts, run the following in a Command Prompt window.

    cd [Program Files]\Common Files\Microsoft Shared\web server extensions\12\BIN
    stsadm -o editssp -title [SSPName] -setaccounts [accounts to add]
    

    The following example adds two user accounts for processing in a SSP named SharedServices1.

    stsadm -o editssp -title SharedServices1 -setaccounts "domain\user1, domain\user2"
    

Installing and Configuring the Sample Web Application

When you download and install the Project 2007 SDK to the default location, the sample Web application is located in the path C:\2007 Office System Developer Resources\Project 2007 SDK\Code Samples\Impersonation\ImpersonationWebApp. Copy all of the files to the directory you created in Procedure 1. You must also copy the Project Server and SharePoint assemblies that your application needs to the Bin subdirectory, and then set the validation key for the Web application.

NoteNote

The sample Web application requires that you use Procedure 1 to create a Web site on the Project Web Access computer.

An impersonation Web application must use the same validation key that Project Web Access uses. The Web application uses the machineKey element in web.config for encryption, decryption, and message authentication checks on ViewState data and Forms authentication tickets. You can declare the machineKey element at the computer, site, or application levels, but not at a subdirectory level. If you do not specify the correct machineKey attributes, then you get a ViewState error.

Other necessary additions to web.config include the list of trusted assemblies in the assemblies element, and the authentication, customErrors, and trust elements.

Finally, you must change the string constants for your server name, SSP name, and SSP port.

Procedure 2. To install and configure the sample Web application:

  1. Copy all of the files from the ImpersonationWebApp subdirectory in the SDK download to the directory you created for the Web application in Procedure 1, for example C:\Project\Impersonation.

  2. Copy the following files to the Bin subdirectory in the Impersonation Web site:

    • Microsoft.Office.Project.Server.Library.dll (copy from [Program Files]\Microsoft Office Servers\12.0\Bin)

    • Microsoft.SharePoint.dll (copy from [Program Files]\Common Files\Microsoft Shared\web server extensions\12\ISAPI)

    • Microsoft.SharePoint.Search.dll

    NoteNote

    For installation on a different SharePoint farm or a Web server outside the farm that contains Project Web Access, you do not need to copy the SharePoint assemblies. The Web application must use a hard-coded ID for the Project Web Access site.

  3. Start Microsoft Visual Studio 2005, and then open the Impersonation Web site from the local IIS server.

  4. In web.config for the Impersonation site, set the machineKey attributes for your Project Server computer. Copy the complete <machineKey ...> line from the web.config file in the top-level site for Project Web Access (typically the default Web site), and replace the <machineKey … > line in the web.config file of the Impersonation site. Keep the element all on one line.

    NoteNote

    In the following example, the machineKey element is broken into multiple lines only for readability.

    <system.web>
       . . .
       <machineKey validationKey="7C9DF8E41A03170EFF870936E0FED824859E541C6CF5768F" 
          decryptionKey="EAAECB67BFF6AED2F4F812ADE1967CB6AB33A94A9FDE400C" 
          validation="SHA1" />
       . . .
    </system.web>
    
  5. Add the trusted assemblies required by your application to the <assemblies> element in web.config. Type assembly in the Run dialog box to see the registered assemblies, and then get the information you need from the Properties page of each assembly. Also set the debug attribute of the compilation element to true while you are developing the application. For example, add the following (the add elements should be all on one line).

    <compilation debug="true">
    <assemblies>
                            . . .
    <add assembly="Microsoft.Office.Project.Server.Library, 
                               Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C"/>
    <add assembly="Microsoft.SharePoint.Search, 
                               Version=12.0.0.0, Culture=neutral, PublicKeyToken=71E9BCE111E9429C"/>
    </assemblies>
    </compilation>
    
  6. Set the authentication element as follows.

    <authentication mode="Windows"/>
    
  7. Set the customErrors element in web.config to the value your application requires. For example, the sample Web application uses the following line to return complete exception information to all callers during development of the application.

    <customErrors mode="Off"/>
    
  8. Set the trust element in web.config to allow unrestrictred access to local resources, as shown in the following example.

    <trust level="Full"/>
    
  9. Set the sessionState element in web.config, as needed. For example, the sample Web application uses the default mode InProc, which stores session state in memory on the Web server. AutoDetect is the default value of the cookieless attribute in ASP.NET 2.0. The sample Web application uses a non-default cookieName attribute.

    <sessionState mode="InProc" cookieless="AutoDetect" cookieName="P12_ImpersonationTest" />
    
    NoteNote

    The InProc mode enables the Session_End method in the Global class (Procedure 4, Step 6).

  10. In ImpersonationUtils.cs (App_Code subdirectory), change the SERVER_NAME constant to use the Project Web Access server name. Change the name of the SSP from SharedServices1 to the correct name for the SSP for Project Web Access, and change the SSP port value if necessary.

    To see the actual URL of a PSI Web service on a Project Web Access computer, expand the Office Server Web Services node in IIS Manager Web sites, expand the SSP virtual directory node, click the PSI node, right-click a Web service, and then click Browse. For example, Internet Explorer shows the following URL when you browse Project.asmx on the Project Web Access computer.

    http://localhost:56737/SharedServices1/PSI/Project.asmx
    
  11. For easy access, click Save All and save the solution file as Impersonation.sln in the path C:\Project\Impersonation.

  12. If you are installing the Impersonation application on a different server than the Project Web Access computer, do the following:

    1. In Impersonate.aspx.cs, uncomment the following line:

      private const string PWA_SITE_GUID = "c2cc4059-51e6-418a-8f16-33b104f5799e";

    2. To find the Project Web Access site ID, open the Shared Services Administration page on the Project Web Access computer, and then click Project Web Access Sites. Click the drop-down list for the Project Web Access site you want, and then click Edit. The site ID is the GUID of the id option in the URL. For example, in the following URL, the site ID is c2cc4059-51e6-418a-8f16-33b104f5799e.

      http://ServerName:19466/ssp/admin/_layouts/createpwa.aspx?task=Edit&id=c2cc4059-51e6-418a-8f16-33b104f5799e
      
    3. Replace the site ID value of the PWA_SITE_GUID constant.

    4. In the cmdImpersonate_Click event handler, uncomment the line pwaSiteId = new Guid(PWA_SITE_GUID); and comment out the next two lines that use the Microsoft.SharePoint.SPSite site variable. You cannot use the SharePoint assembly when the Web application is not part of the Project Web Access farm.

      NoteNote

      Instead of hard-coding a site ID into the application for a remote computer, you can get the Project Web Access site ID from the SiteData Web service in Windows SharePoint Services. For more information, see the Getting Site ID for Impersonation in Windows SharePoint Services Infrastructure for Project Server.

Creating the PSI Proxy and Derived Classes

Every impersonation application needs a proxy class for each PSI Web service it uses, to create derived classes that override the GetWebRequest method. You can use wsdl.exe to generate the Web service proxies.

The derived classes also need methods to get and set the impersonation context. You create the proxy and derived classes for the sample Web application in the same way as for the console application described in How to: Write a Simple Impersonation Application.

Procedure 3. To create the PSI proxy and derived classes:

  1. In a Visual Studio 2005 Command Prompt window, run a wsdl.exe command that specifies the namespace you are using, language, output file, and Web service URL. For example, after you change the name of ServerName and ProjectServerName, run the following command (all on one line).

    wsdl /n:Microsoft.SDK.Project.Samples.Impersonation /language:c# /out:ProProxy.cs http://ServerName/ProjectServerName/_vti_bin/psi/project.asmx?WSDL
    

    Similarly, create the proxy class source files for the Resource, LoginForms, and Security Web services. The proxy source files in the sample Web application are named ProProxy.cs, ResProxy.cs, LoginFrmProxy.cs, and SecurityProxy.cs.

    NoteNote

    Though the sample application contains the source files, you should create the proxy source files from your own Project Server system to ensure the application uses the installed Project Server build.

  2. Add the proxy source files to the App_Code subdirectory in the Web application.

  3. For each Web service you use, add a class that extends the primary class of the Web service. To add the necessary HTTP headers for using the PSI with impersonation, override the GetWebRequest method. For example, the sample application uses the ProjectDerived class as a proxy for the Project Web service instead of the original proxy you created in Step 1. Add the ProjectDerived class that inherits from the Project class, as follows.

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web.Services;
    using System.Net;
    using PSLibrary = Microsoft.Office.Project.Server.Library;
    
    namespace Microsoft.SDK.Project.Samples.Impersonation
    {
        public class ProjectDerived : Project
        {
            private static String contextString = String.Empty;
    
            protected override WebRequest GetWebRequest(Uri uri)
            {
                // Override the GetWebRequest method and add the two Web request headers
                WebRequest webRequest = base.GetWebRequest(uri);
                if (contextString != String.Empty)
                {
                    webRequest.UseDefaultCredentials = true;
    
                    webRequest.Credentials = CredentialCache.DefaultNetworkCredentials;
    
                    webRequest.Headers.Add("PjAuth", contextString);
                    webRequest.Headers.Add("ForwardFrom", "/_vti_bin/psi/project.asmx");
    
                    webRequest.PreAuthenticate = true;
                }
                return webRequest;
            }
            . . .
        }
    }
    
  4. Add methods to get and set the impersonation context. When you run the application, the userAccount value must be a valid Project Server user, of the form domain\username or aspnetsqlmembershipprovider:username. If the latter, isWindowsUser must be false. The siteId parameter is the Project Web Access site ID. The lcid parameter contains the locale ID, for example 1033 for English.

    public static void SetImpersonationContext(bool isWindowsUser, String userAccount, 
        Guid userGuid, Guid trackingGuid, Guid pwaSiteId, String lcid)
    {
        contextString = GetImpersonationContext(isWindowsUser, userAccount, 
            userGuid, trackingGuid, pwaSiteId, lcid);
    }
    
    private static String GetImpersonationContext(bool isWindowsUser, String userAccount, 
        Guid userGuid, Guid trackingGuid, Guid pwaSiteId, String lcid)
    {
        PSLibrary.PSContextInfo contextInfo = 
            new PSLibrary.PSContextInfo(isWindowsUser, userAccount, 
                                        userGuid, trackingGuid, pwaSiteId, lcid);
        String contextString = PSLibrary.PSContextInfo.SerializeToString(contextInfo);
        return contextString;
    }
    
  5. Before you call any PSI method using impersonation, set the impersonation context. The following example is in the cmdListProject_Click event handler for the Impersonate page.

    Guid trackingGuid = new Guid();
    Proxy.ProjectDerived.SetImpersonationContext(isImpersonatingWindowsUser, 
        selectedUserAccount, impersonatedUserUid, trackingGuid, 
        pwaSiteId, LANGUAGE_ID);
    

Figure 2 shows a simplified diagram of how the sample impersonation Web application handles authentication, authorization, and calls to the PSI. Because the sample Web application is installed on the Project Server computer, Project Web Access and the PSI Web services are on the same computer.

NoteNote

An impersonation Web application need not be on the same server as Project Web Access and the SSP for the PSI Web services. We recommend that you install an impersonation application on the same server as the Web front end for Project Server (a Project Web Access server). If Project Server is separate from the Project Web Access server, the application can make direct calls to the PSI Web services through the SSP on a second server. Figure 2 shows only the simplest configuration where all components are on a single server. For a diagram of applications that use a separate Project Web Access server, see Figure 2 in Project Server Architecture.

Figure 2. Authentication and authorization for an impersonation Web application

Authentication for an impersonation application

The general steps in Figure 2 outline how an impersonation application works with Project Server. The steps note where general impersonation applications for Project Server can differ from the sample Web application.

To understand the following steps in detail, step through the application using the Visual Studio debugger.

  1. The remote browser sends Windows credentials of the user to IIS on the Project Server computer.

  2. The LoginName control on the Impersonation page in the sample Web application shows the user's Windows credentials.

    NoteNote

    It is not necessary for a Web application to log on through Project Web Access. If the application uses only impersonation, it must have the security restrictions required for your organization. The sample application is designed to demonstrate the differences between the application user, a logged on user, and an impersonated user.

  3. When the application user chooses Windows authentication, the application assumes the user account is also a Project Server account. If the Windows account is not a Project Server user, the application fails at Step 4.

    For a Forms user, the application user logs on Project Server using Forms authentication. Windows SharePoint Services handles authentication for both Windows and Forms logon. Forms logon goes through the LoginForms Web service URL of Project Web Access (for example, http://ServerName:81/pwa/_vti_bin/psi/LoginForms.asmx).

    ASP.NET returns the Forms authentication cookie to the application, which sets the CookieContainer value of all the derived PSI Web service objects it uses (ProjectDerived, ResourceDerived, and SecurityDerived).

  4. The application sets the Url parameter of all the proxy PSI Web service objects. For example, the Resource Web service for a Windows user has the following Url parameter with port 80 (the default Project Web Access port):

    http://ServerName/ProjectServerName/_vti_bin/PSI/Resource.asmx
    

    If Project Web Access uses port 81 for Forms users, the Security Web service for a Forms user has the following Url parameter:

    http://ServerName:81/ProjectServerName/_vti_bin/PSI/Security.asmx
    

    The application then calls ReadResources through the proxy Resource Web service, and then fills the DropDownList control with the result. The application also calls CheckUserGlobalPermissions for the logged-on user to show the ManageQueue, NewProject, and ViewProjectCenter permissions.

  5. Project Web Access forwards the ReadResources and CheckUserGlobalPermissions calls to the Resource and Security PSI Web services in the SSP site. The PSI Web services can be on a separate Project Server computer.

  6. When the application user selects a resource in the drop-down list and clicks Impersonate, the application calls ReadResources again through Project Web Access to get information about the selected resource. The information includes the resource GUID, account name (domain\username or AspNetSqlMembershipProvider:username), and whether to use Windows or Forms authentication.

    The application sets the Url parameter for all of the Web services it uses to the direct URL of the PSI in the SSP, for example, http://ServerName:5636/SharedServices1/PSI/Resource.asmx.

    For every PSI call on behalf of the impersonated user, the application first calls SetImpersonationContext in the derived Web service object.

    NoteNote

    You can create an impersonation application that independently checks user credentials, sets the impersonation context for all entities, and calls the PSI only through the direct Web service address of the SSP. It is not necessary to call the PSI through Project Web Access.

  7. All calls to the PSI using impersonation must go directly to the SSP, not through Project Web Access, and must include the PjAuth context string in the header of the SOAP Web request. For an example of the impersonation context string, see Debugging the Web Application.

Maintaining State for PSI Calls and Other Objects

The sample application uses ViewState data to maintain information for the Impersonate page variables. Code in the Global class maintains utility objects that handle exceptions for the application and store the logon state and PSI URL for each user session.

Using ViewState

To maintain state for simple settings when the Impersonate page refreshes, you can add a field to the page ViewState data. For example, to determine whether the user is impersonating a resource, add the following line to the end of the cmdImpersonate_Click event handler for the Impersonate button.

ViewState.Add("impersonating", "Yes");

If a user successfully logs on, the application calls ReadResources through the Project Web Access URL to get a ResourceDataSet (named rds in the following code). In addition to populating the userList drop-down list with all of the resource names, the application populates the static userHashTable object with the key-value pairs of resource name and account name for each row in the Resources table, as follows:

private static Hashtable userHashTable = new Hashtable();
. . .
foreach (Proxy.ResourceDataSet.ResourcesRow resourceRow in rds.Resources)
{
    if (resourceRow["WRES_ACCOUNT"].ToString() != "")
    {
        userHashTable.Add(resourceRow.RES_NAME, resourceRow.WRES_ACCOUNT);
        userList.Add(resourceRow.RES_NAME);
    }
    // Set the current user name here
}

Every time the Impersonate page refreshes, code in the Page_Load event handler determines how to set the Enabled and Text properties of controls. If the user is logged on, the page retrieves the value of the impersonated user account from the hash table.

if (ViewState["impersonating"] != null 
    && ViewState["impersonating"].ToString() == "Yes")
{
    cmdListProjects.Enabled = true;
    cmdCreate.Enabled = true;
    txtProjName.Enabled = true;

    if (isLoggedOn)
        selectedUserAccount = 
            userHashTable[lbxUsers.SelectedItem.Text].ToString().ToLower();
}
else
{
    cmdListProjects.Enabled = false;
    cmdCreate.Enabled = false;
    txtProjName.Enabled = false;
    lblPsiCalled.Text = string.Empty;
    txtProjName.Text = string.Empty;

    if (!IsPostBack)
    {
       // Add initialization code here for the first time the page renders
    }
}

Using Global.asax

To handle common exceptions, the sample Web application uses utility routines in a separate class. The application can use the same exceptionHandlers object for all session users. Therefore, you can use the Application_Start method in global.asax to instantiate the ExceptionHandlers class.

The ImpersonationUtils class maintains the state of the session user such as IsImpersonating and the properties of each PSI proxy object. You can use the Session_Start method in global.asax to create a utils object for each session user, and then destroy the object in the Session_End method.

For example, the Url property determines whether you make regular calls to the PSI or use the SSP to make calls during impersonation. When you log on Project Server with the Impersonation application, calls to the PSI use PROJECT_SERVER_URI for the value of the ResourceDerived.Url property. For Forms logon, the application sets the Url property to PROJECT_SERVER_FORMS_URI. During impersonation, for PSI calls to the ProjectDerived and SecurityDerived objects, the application sets the Url property to SSP_URI.

When you add a Global application class file (typically global.asax) to a Web application, the file includes Application_Start, Session_Start, and other common methods within the <script> tag. For more flexibility in coding, you can create the global.asax.cs file with the Global class and move all of the methods in global.asax to global.asax.cs. The steps in Procedure 4 are already done in the sample Web application.

Procedure 4. To maintain global state:

  1. Add a Global application class file named global.asax to the Web application. The new global.asax file contains the following.

    %@ Application Language="C#" %>
    
    <script runat="server">
        void Application_Start(object sender, EventArgs e) 
        {
            // Code that runs on application startup
        }
        // Other application and session methods . . .
    </script>
    
  2. Add a class file named global.asax.cs with the class Global that inherits from System.Web.HttpApplication. Visual Studio prefers to save class files in the App_Code subdirectory. Add the namespace for your application and static variables for the objects you need, as follows.

    using System;
    using System.Data;
    using System.Configuration;
    using System.Web;
    using System.Web.Security;
    
    namespace Microsoft.SDK.Project.Samples.Impersonation
    {
        /// <summary>
        /// Manages (creates/destroys) the global objects for the Impersonation Web application.
        /// </summary>
        public class Global : HttpApplication
        {
    
            public static ImpersonationUtils utils; 
            public static ExceptionHandlers exceptionHandlers;
    
            public Global()
            {
            }
            // Cut methods from global.asax and paste them here.
        }
    }
    
  3. Cut the Application_Start, Application_End, Application_Error, Session_Start, and Session_End methods from global.asax, and paste them into the Global class in global.asax.cs.

  4. To enable global.asax to use the Global class, modify the Application directive in global.asax as follows.

    <%@ Application Language="C#" Inherits="Microsoft.SDK.Project.Samples.Impersonation.Global" %>
    

    If you prefer to put global.asax.cs under global.asax rather than in the App_Code subdirectory, instead of the Inherits attribute you could add the following attribute to the Application directive in Global.asax.

    CodeBehind="Global.asax.cs"
    
  5. Manage the exceptionHandlers object as follows.

    void Application_Start(object sender, EventArgs e)
    {
        // All sessions can share the exceptionHandlers object.
        exceptionHandlers = new ExceptionHandlers();
    }
    
    void Application_End(object sender, EventArgs e)
    {
        exceptionHandlers = null;
    }
    
  6. Manage the utils object as follows:

    void Session_Start(object sender, EventArgs e)
    {
        // Create a unique ImpersonationUtils object for each user session
        // because it maintains the state for impersonation, such as the 
        // IsImpersonating property and the URL of each Web service proxy object.
        utils = new ImpersonationUtils();
    }
    
    void Session_End(object sender, EventArgs e)
    {
        // Code that runs when a session ends. 
        // Note: The Session_End event is raised only when the sessionstate mode
        // is set to InProc in the web.config file. If sessionstate mode is set to StateServer 
        // or SQLServer, the event is not raised.
    
        utils = null;
    }
    

Using the GridView Control with a DataSet

It is a straightforward process to create a paged GridView control that uses a DataSet object. The list of draft projects on the Impersonate page uses a GridView control named grdProjects. The control properties include AllowPaging and AllowSorting set to true. The PageSize property controls the number of rows to show on each grid page.

The sample application gets a list of all projects in the Draft database using the ReadProjectStatus (or the ReadProjectList) method. The Select method of a ProjectDataTable can use string parameters to return filtered and sorted data.

When a user clicks a page number in the grid, the grdProjects_PageIndexChanging event handler contains code that shows the new grid page. Procedure 5 shows how to fill a ProjectDataSet with a list of projects, filter and sort the data, and page through the data using a GridView control.

Procedure 5. To use a GridView control with a DataSet object:

  1. Create strings with the desired filter and sorting criteria. For example, the following class constants are used as parameters in the Select method to remove the ProjectDataTable row with an empty PROJ_UID and to sort the ProjectDataTable on the PROJ_NAME column.

    private const string ROW_FILTER = 
        @"PROJ_UID <> '00000000-0000-0000-0000-000000000000'";
    private const string SORT_COLUMN = "PROJ_NAME";
    
  2. Create a DataSet object. In the sample application, the Impersonate class variable named dsProject is a ProjectDataSet.

    using Proxy = Microsoft.SDK.Project.Samples.Impersonation;
    . . .
        private static Proxy.ProjectDataSet dsProject = new Proxy.ProjectDataSet();
    
  3. Fill the DataSet object with the information you need. For example, the cmdListProject_Click event handler uses the Merge method to get all projects of all types in the Draft database.

    string projectName = "";
    Guid projectGuid = Guid.Empty;
    
    dsProject.Merge(Proxy.Global.utils.ProjectProxy.ReadProjectStatus(
        projectGuid, Proxy.DataStoreEnum.WorkingStore, 
        projectName, (int)PSLibrary.Project.ProjectType.Project));
    
    dsProject.Merge(Proxy.Global.utils.ProjectProxy.ReadProjectStatus(
        projectGuid, Proxy.DataStoreEnum.WorkingStore, 
        projectName, (int)PSLibrary.Project.ProjectType.LightWeightProject));
    
    dsProject.Merge(Proxy.Global.utils.ProjectProxy.ReadProjectStatus(
        projectGuid, Proxy.DataStoreEnum.WorkingStore, 
        projectName, (int)PSLibrary.Project.ProjectType.MasterProject));
    
    dsProject.Merge(Proxy.Global.utils.ProjectProxy.ReadProjectStatus(
        projectGuid, Proxy.DataStoreEnum.WorkingStore, 
        projectName, (int)PSLibrary.Project.ProjectType.InsertedProject));
    
  4. Set the DataSource property of the GridView control to the DataRow array returned by the DataTable.Select method. In the example, Project is of type ProjectDataSet.ProjectDataTable.

    grdProjects.DataSource = dsProject.Project.Select(ROW_FILTER, SORT_COLUMN);
    grdProjects.DataBind();
    
  5. Create a PageIndexChanging event handler for the grid. Set the PageIndex property of the grid to the page number that the user clicked, and then set the DataSource value to the same value you used in the previous method. The DataTable.Select method uses the same filter and sorting parameters.

    protected void grdProjects_PageIndexChanging(object sender, GridViewPageEventArgs e)
    {
        grdProjects.PageIndex = e.NewPageIndex;
    
        grdProjects.DataSource = dsProject.Project.Select(ROW_FILTER, SORT_COLUMN);
        grdProjects.DataBind();
    }
    

Debugging the Web Application

Compile the Impersonation Web application and fix any compilation errors. After it compiles correctly, the Impersonation Web application should work. For example, if you used port 5636 for the site, the application URL is http://ServerName:5636.

Test the site on the local Project Server computer and on a remote computer. Following are some common problems:

  • If the Impersonation application works on the local Project Server computer but not on a remote computer, the IISWebServer tag (Procedure 1, Step 10) may not be correct.

  • If you get an HTTP 401 (unauthorized) exception when you first try to log on with a Windows account, check that the application pool owner is set properly (Procedure 1, Steps 6 and 7).

To debug the Web application, use Visual Studio 2005 and attach the debugger to all of the w3wp.exe processes on the Project Server computer. Procedure 6 shows how to find the list of projects in the ProjectDataSet when a remote user clicks the List Draft Projects button on the Impersonate page. The procedure for debugging the Web application is similar to the debugging procedure in Walkthrough: Creating a Custom Project Server Web Part.

Procedure 6. To debug the Web application:

  1. If you are debugging from a remote computer, install and run the Microsoft Visual Studio Remote Debugging Monitor on the Project Web Access computer. If the development computer is running Microsoft Windows XP SP2 or Windows Vista, you must unblock TCP Port 135 and UDP 4500 / UDP 500. For more information, see Configure Firewall for Remote Debugging (http://msdn2.microsoft.com/en-us/library/h0d7tte4(VS.80).aspx) and How to: Set Up Remote Debugging (http://msdn2.microsoft.com/en-us/library/bt727f1t.aspx).

  2. In Visual Studio, open the Impersonation Web application, and on the Debug menu, click Attach to Process.

  3. In the Attach to Process dialog box, select the Default transport and browse to the Project Web Access computer name for the Qualifier. Click Select, and then in the Attach to options, select only the Managed box.

  4. Select Show processes from all users and Show Processes in all sessions.

  5. To put Visual Studio into debug mode, in the Available Processes list, press CTRL+click for all of the w3wp.exe processes, and then click Attach.

  6. Open the Impersonate.aspx.cs file and put a breakpoint on the if (isReadProjectStatus) line in the cmdListProjects_Click event handler.

  7. Have the remote user navigate to http://ServerName:5636 in Internet Explorer, click Impersonation in the side pane menu, log on, select a user, and then click Impersonate.

  8. When the remote user clicks List Draft Projects, Visual Studio reaches the breakpoint. Trace through the code using F10 until the dsProject variable is assigned a value.

  9. Click the magnifying glass icon that appears when you pause the mouse pointer over dsProject, and then click DataSet Visualizer.

  10. In the DataSet Visualizer dialog box, select the Project table in the drop-down list.

The Project table in the ProjectDataSet contains the list of projects and all of the project fields; the following example shows only the first three columns of the table.

PROJ_UID

PROJ_NAME

PROJ_TYPE

1bd083bb-8791-4ea4-8a9e-dacd460b0558

TestProj47

0

7fa085f3-e20e-40de-9adb-780cf2bf37d7

Joes project

0

00000000-0000-0000-0000-000000000000

5

The project with the empty GUID and empty project name is of PROJ_TYPE 5, which the Project.ProjectType enumeration shows is an inserted project. However, the empty inserted project is an internal artifact, so the application uses ROW_FILTER in the filterExpression parameter of the Project.Select method to remove that row when it assigns the data to the DataGrid.

You can also check the value of the Web request header for a PSI call. For example, set a breakpoint in the ProjectDerived class, at the line webRequest.PreAuthenticate = true, and then run the application and impersonate the administrator user. When you click List Draft Projects, the Visual Studio debugger breaks execution and shows the following value for the Web request header (broken into separate lines here for readability):

{User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; MS Web Services Client Protocol 2.0.50727.42)

PjAuth: un%3d%22aspnetsqlmembershipprovider%3aadministrator

%22+ug%3d%221089d3d5-e8f9-4605-9e0d-9338c7e4a8fd

%22+tg%3d%2200000000-0000-0000-0000-000000000000

%22+sg%3d%22c2cc4059-51e6-418a-8f16-33b104f5799e%22+lc%3d%221033

%22+wu%3d%22false%22+uz%3d%220%22+v%3d%220%22+

ForwardFrom: /_vti_bin/psi/project.asmx

The first line is the standard header. The application adds the PjAuth section, which includes the impersonated user account and the SharePoint site ID of Project Web Access (in this case, c2cc4059-51e6-418a-8f16-33b104f5799e), and the ForwardFrom header section. When the application calls ReadProjectStatus or ReadProjectList using the SSP address, the PSI uses the PjAuth header information for authorization and calls the underlying Project business object with the impersonated user's permissions.

NoteNote

The ForwardFrom header section was used in prerelease versions of Project Server, but is no longer required. We retain it in the sample code to show that you can add Web request header information that is not used, or that might be used in the future.

Next Steps

Thoroughly test the Impersonation Web application you create. When it is satisfactory, set the compilation debug attribute to false and the custom errors mode attribute to a value you want in web.config. Then, publish the application to a production site. Publishing a site precompiles the code and installs only the essential files for the Web application on the production site.

See Also

Tasks

How to: Write a Simple Impersonation Application

Walkthrough: Creating a Custom Project Server Web Part

Concepts

Using the ProjTool Test Application

Project Server Architecture

Windows SharePoint Services Infrastructure for Project Server

Other Resources

Developer Center: ASP.NET

Developer Portal: Project

What's New in Publishing Web Sites in Visual Studio

Configure Firewall for Remote Debugging

How to: Set Up Remote Debugging

ASP.NET Debugging and Troubleshooting