Telerik blogs

Note: A Knowledge Base article exists with a link to the sample code used in this post.

Note: A previous blog post was written with some basic information regarding TeamPulse integration.

 

The 2011 R3 release of TeamPulse has brought several significant changes to the product, and chief among them from an integration standpoint are the inclusion of forms-based authentication and role-based permissions. These changes extend beyond the TeamPulse client interface right through the services below, which means that any requests to read or write data must be accompanied by a proper authentication cookie representing a user that has the required permissions on the impacted data itself. This blog post aims to provide guidance for anyone interested in integrating their own custom .NET applications with the TeamPulse data service.

 

Caution: Great care must be taken when making changes to the data.  The OData layer does not contain all of the business logic that surrounds data manipulation in TeamPulse.  If you are not sure about potential consequences to data changes you are looking to making then feel free to contact support and we'll be happy to advise you.

 

The first step is to add a service reference to the TeamPulse data service, located at http://<TeamPulse server name>:9898/Services/DataService.svc. The metadata for the service is unsecured, so doing this is standard practice within Visual Studio.

 

Once the service reference is added, it is necessary to instantiate an instance to make requests against. The following code snippet shows how (keep in mind the serviceUrl will be specific to your local environment and likely different from that shown):

// Compose the URL to the data service
var serviceUrl = "http://localhost/TeamPulse/Services/DataService.svc";
 
// Instantiate an instance of the data service context
dataContext = new TitanDataContext(new Uri(serviceUrl, UriKind.RelativeOrAbsolute));

 

At this point you would normally be ready to start making requests to the service, however those requests will now fail because the service will not be able to authenticate or authorize the incoming request. In order to satisfy this constraint we will need to attach a cookie to all of our requests, so lets add an event handler to the context that will allow us to perform an action before each request is actually sent to the service:

// Connect a handler to the SendingRequest event that will attach our auth cookie to the request
dataContext.SendingRequest += BeforeSendingRequest;

 

Inside the BeforeSendingRequest method we will attach a different cookie based on the type of authentication we want to use. TeamPulse now supports standard Windows authentication as well as forms-based authentication. Lets see how that looks:

/// <summary>
/// Attaches a valid authentication cookie to each data service request.
/// </summary>
/// <param name="sender">Event sender</param>
/// <param name="e">Event arguments</param>
private static void BeforeSendingRequest(object sender, SendingRequestEventArgs e)
{
    string cookie = string.Empty;
 
    if (useWindowsAuth)
    {
        cookie = GetWindowsCredsAuthCookie(useDefaultWindowsCredentials, username, password, domain);
    }
    else
    {
        cookie = GetAuthenticationCookie(username, password);
    }
 
    e.RequestHeaders.Add("Cookie", cookie);
}


As you can see we are simply determining what type of authentication to use (based on a boolean set elsewhere in the app), calling a method that will generate the necessary cookie, and then adding the cookie into the request header. Lets take a look at how the creation of a cookie is performed for each authentication type.

 

Creating a Windows Authentication Credential Cookie

It is possible to acquire a valid Windows Authentication cookie by simulating a request to the TeamPulse windows login page and then retrieving the cookie from the response. Let's take a look at building the proper request, and then we can follow that with a look at how to deal with the reponse.

 

Building the Windows Auth Request 

We will use the System.Net.HttpWebRequest class to create our request, but to do so we need to tell it the url of the TeamPulse windows login page. The following example shows such a request, but keep in mind once again that your host url will most likely be different than the one seen below.

string teamPulseRootUrl = "http://localhost/TeamPulse";
string winLoginPageAddress = teamPulseRootUrl + "/WinLogin/Login.aspx?ReturnUrl=0";
 
var request = (HttpWebRequest)HttpWebRequest.Create(winLoginPageAddress);

 

Once we have a request object, we need to set up a couple basic properties:

request.Method = "GET";
request.PreAuthenticate = true;
request.AllowAutoRedirect = false;

Now we can set the credentials information. If you wish to simply use default credentials based on the user executing the application, the following code will suffice:

request.UseDefaultCredentials = true;
request.Credentials = CredentialCache.DefaultCredentials;

 

However it is also possible to specify alternate credentials by writing code similar to below:

request.UseDefaultCredentials = false;
request.Credentials = new NetworkCredential(username, password, domain);

Performing the Request and Parsing the Response

At this point we have a request that is ready to be delivered. The following line will execute the request and return a response:

var response = (HttpWebResponse)request.GetResponse();

Now for the tricky part: we need to find the appropriate authentication cookie within the response headers. The following code will look through the headers and pull out the cookie if authentication was successful:

if (response.Headers["Set-Cookie"] != null)
{
    var cookies = response.Headers["Set-Cookie"].Split(new char[]{';', ','}, StringSplitOptions.RemoveEmptyEntries);
 
    foreach (string c in cookies)
    {
        if (!string.IsNullOrWhiteSpace(c))
        {
            var trimmedc = c.Trim();
            if (trimmedc.StartsWith(".ASPXAUTH="))
            {
                authenticationCookie = trimmedc;
                break;
            }
        }
    }
    if (authenticationCookie == null)
    {
        throw new SecurityException("Invalid credentials");
    }
}

We could then return the authenticationCookie object back to our BeforeSendingRequest method where it would be attached to the request.

Creating a Forms Authentication Credential Cookie

If you wish to authenticate with a forms user, the pattern is similar but the code is slightly different. At the high-level, our steps are the same: build a request, then retrieve the authentication cookie from the response.

 

Building the Forms Auth Request 

We will once again be building a System.Net.HttpWebRequest, however rather than hitting a specific page on the TeamPulse web host, we will instead be making a POST request to the Forms Authentication Provider:

string teamPulseRootUrl = "http://localhost/TeamPulse";
string loginServiceAddress = teamPulseRootUrl + "/Authentication_JSON_AppService.axd/Login";
 
var request = HttpWebRequest.Create(loginServiceAddress);

Now we set the request to be a POST request of type JSON:

request.Method = "POST";
request.ContentType = "application/json";

Next we create a JSON string representation of our Forms credentials, and assign the length of that value to the ContentLength property on the request:

var body = string.Format(
                "{{ \"userName\": \"{0}\", \"password\": \"{1}\", \"createPersistentCookie\":false}}",
                username, password);
 
request.ContentLength = body.Length;

Finally, we need to write the JSON string into the request stream, as shown here:

var stream = request.GetRequestStream();
using (var sw = new StreamWriter(stream))
{
    sw.Write(body);
}

Performing the Request and Parsing the Response

Once again we are ready to make our request and get our authentication cookie out of the response header. This time it is much easier, however, as our response will only return us at most the one cookie we are looking for (and none if the authentication fails). Here is the code to send the request and grab the cookie from the response in its entirety:

var response = request.GetResponse();
if (response.Headers["Set-Cookie"] != null)
{
    authenticationCookie = response.Headers["Set-Cookie"];
}
else
{
    throw new SecurityException("Invalid credentials");
}

We could then return the authenticationCookie object back to our BeforeSendingRequest method where it would be attached to the request.

Now Why Were We Doing All This Again?

This all started back when we wanted to make a request to the data service. Now that we have attached a cookie to the request, we can start doing all the great stuff that oData services allow us to do. We can fetch data, we can update data, we can even create new data. Of course, the user we are connecting with will need to have appropriate permissions to perform the requested actions. So, for example, if you want to create a new story in Project 1, the user you connect with will need Add Story rights for Project 1 in order for that action to succeed.

You can find a sample project into the KB mentioned at the top - Accessing TeamPulse data via the OData service. That sample project now also includes all of the authentication code shown in this post. Combined this code should form an excellent starting point for anyone interested in creating a .NET application that interacts with TeamPulse data.


Comments

Comments are disabled in preview mode.