Using the new TFS 2010 impersonation APIs

I just had a run in with TFS 2010’s new impersonation APIs and thought I would share.  

My customer wanted to add some functionality to generate release notes and update the associated workitem for those release notes. The thought was since some of the information for a release was in a SQL Server DB and some of this information was in TFS that a simple web app could be created using delegation to accomplish this with Windows security preserving the user on the workitem and the connection to the database as the user running the tool.. While that was true for SQL Server and TFS 2008, TFS 2010 has new impersonation APIs to help you achieve this without delegation.

This scenario is roughly documented here.  You have to go down to the section that reads “Using a Combination of Techniques” to get to this particular scenario.

When using Windows auth & delegation, you need to create a domain user that has been granted access to TFS and the “Make requests on the behalf of others” priv on both the TFS app tier and the TFS collection you need to use. This can be done in the TFS Administration Console. Once you have done that, you will need a couple of helper classes:

public class NetworkCredentialsProvider : ICredentialsProvider

{

   private readonly NetworkCredential credentials;

   public NetworkCredentialsProvider(NetworkCredential credentials)

   {

       this.credentials = credentials;

   }

   public ICredentials GetCredentials(Uri uri, ICredentials failedCredentials)

   {

       return this.credentials;

   }

   public void NotifyCredentialsAuthenticated(Uri uri)

   {

   }

}

 

And

public class ImpersonatedCollection

{

    /// <summary>

   /// Returns an impersonated collection to perform work for on a TFS

   /// 2010 server. Impersonator is hard-coded with a password in this class.

   /// </summary>

   /// <param name="collectionToUse">

   /// Collection to impersonate on

   /// </param>

   /// <param name="userToImpersonate">

   /// User to impersonate. Should be of the form "DOMAIN\Username"

   /// </param>

   /// <returns>

   /// TfsTeamProjectCollection

   /// </returns>

   public static TfsTeamProjectCollection CreateImpersonatedCollection(

      Uri collectionToUse, string userToImpersonate)

   {

      // Get the credentials of the impersonator.

      // Hard code the credentials here - username, password, domain

      NetworkCredential cred =

         new NetworkCredential("username", "password", "DOMAIN");

      ICredentialsProvider TFSProxyCredentials =

         new NetworkCredentialsProvider(cred);

      TfsTeamProjectCollection currentCollection =

         new TfsTeamProjectCollection(collectionToUse, cred);

      // Get the TFS Identity Management Service

      IIdentityManagementService identityManagementService =

         currentCollection.GetService<IIdentityManagementService>();

      // Look up the user that we want to impersonate

      TeamFoundationIdentity identity =

         identityManagementService.ReadIdentity(IdentitySearchFactor.AccountName,

            userToImpersonate, MembershipQuery.None, ReadIdentityOptions.None);

      TfsTeamProjectCollection impersonatedCollection =

         new TfsTeamProjectCollection(collectionToUse, cred, TFSProxyCredentials,

         identity.Descriptor);

      return impersonatedCollection;

   }

}

 

This will allow you to write code like this:

protected void Button1_Click(object sender, EventArgs e)

{

   // Get an instance of the impersonated collection using the identity of this user 

   TfsTeamProjectCollection collection =

      ImpersonatedCollection.CreateImpersonatedCollection(

      new Uri("https://TFS2010:8080/tfs/DefaultCollection"),

      WindowsIdentity.GetCurrent().Name);

           

   // Get the WorkItemStore

   WorkItemStore wis =

      (WorkItemStore)collection.GetService(typeof(WorkItemStore));

             

   // Do what you need ...

}

I’ve hard-coded the username, password and domain into the ImpersonatedCollection class static method. You can obviously change this however you like. The safest way to do this would probably be by encrypting the information in the configuration file and reading it from there. I would also add some logging and exception handling as this really just a sample to show how to do this.

There are a couple things you can check that will help you debug this if you have issues.  

1)

After the lookup is done (identityManagement.ReadIdentity), the returned identity.Displayname should be the identity of the person you want to impersonate. In this case since you are using delegation, it should be the delegated user.

2) Once you have the impersonatedCollection returned from the static function, collection.GetAuthenticatedIdentity should return the identity of the user you granted privs to that is the impersonator. 

3) Again with the impersonatedCollection returned from the static function, collection.AuthorizedIdentity.DisplayName should equal the person you want to impersonate. In this case since you are using delegation, it should be the delegated user.

4) There is no ChangeImpersonatedUser() type function. Once a connection to a Team Project Collection is set up for a given user, that user cannot be changed. 

5) Since we allow multiple connections to the same TPC as different users at the same time you have to be explicit about the user you are connecting as each time.