Tuesday, January 31, 2012

Programmatic SiteMinder 2.0 authentication and getting the FedAuth cookie to access SharePoint 2010 service

-->
One of our client upgraded their SSO authentication mechanism for their site from SiteMinder v1 to SiteMinder v2. We have built one project for this client that was making use of SSO to authenticate users and allowing them access. With WSS 3.0 and SiteMinder v1, we didn’t had much issues. We passed the username password, got hold of the SAML token and used it in the cookie container to consume the service from WSS 3.0.

But with sharepoint 2010 (claims enabled) and SiteMinder v2.0, there were a lot of difference. There were around 7-8 redirects before we hit the login page. Then we constructed the data to be posted and posted to the login page. Got the SAML token but it’s not finished yet. There were again a series of redirects before we hit the _Trust link. The trust link is where we post the web context, web result and web authentication data. Successfully posting these 3 data along with required cookies helped to get hold of the much wanted FedAuth cookie. This is the cookie that will help you consume services hosted at sharepoint 2010. Below is the full listing of the code.

public static void UpdateListItem(string listPath, string username, string password)
        {
            CookieContainer cookies = new CookieContainer();
            ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3;
            string message = string.Empty;

            try
            {

                // Open connection to Protected URI.  This will be intercepted and redirected
                // to SiteMinder login screen.
                Uri listUri = new Uri(listPath);
                HttpWebRequest request = WebRequest.Create(listUri) as HttpWebRequest;
                request.AllowAutoRedirect = false;

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

                string url = string.Empty;
                string domain = listUri.Host; //"https://yourdomain.com";
                string userAgent = "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0)"; //very important to set the agent parameter to requests
                Cookie jcookie = null;

                while (response.StatusCode == HttpStatusCode.Redirect)
                {
                    url = response.Headers[HttpResponseHeader.Location];

                    //check headers for Set-Cookie
                    if (!string.IsNullOrEmpty(response.Headers[HttpResponseHeader.SetCookie]))
                    {
                        string[] cookie = response.Headers[HttpResponseHeader.SetCookie].Split(';');

                        string[] cookieNameValue = cookie[0].Split('=');
                        jcookie = new Cookie(cookieNameValue[0], cookieNameValue[1]);
                        jcookie.Domain = listUri.Host;

                        string[] cookiePathValue = cookie[1].Split('=');
                        jcookie.Path = cookiePathValue[1];

                        string cookieIsSecure = cookie[2];
                        if (cookieIsSecure.Trim().ToLower() == "secure")
                            jcookie.Secure = true;

                        cookies.Add(jcookie); // This is one of the cookie that is required when posting data to trust link. Verified using fiddler
                    }

                    request = WebRequest.Create(url) as HttpWebRequest;
                    request.AllowAutoRedirect = false;
                    request.UserAgent = userAgent;

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

                // Create the form data to post back to the server
                NameValueCollection namevalues = GetHTMLInputTags(ResponseToString(response));
                string postData = string.Empty;
                foreach (string key in namevalues.Keys)
                {
                    postData += key + "=" + System.Web.HttpUtility.UrlEncode(namevalues[key]) + "&";
                }
                postData += "postpreservationdata=&";
                postData += "userid=" + System.Web.HttpUtility.UrlDecode(username) + "&";
                postData += "password=" + System.Web.HttpUtility.UrlEncode(password);

                // Submit the data back to SiteMinder
                cookies.Add(response.Cookies);

                request = WebRequest.Create(url) as HttpWebRequest;
                request.CookieContainer = cookies;
                request.ContentType = "application/x-www-form-urlencoded";
                request.ContentLength = postData.Length;
                request.Method = "POST";
                // Important: we need to handle the redirect ourselves
                request.AllowAutoRedirect = false;

                // post the data to the request
                using (StreamWriter sw = new StreamWriter(request.GetRequestStream()))
                {
                    sw.Write(postData); sw.Flush(); sw.Close();
                }

                // Important to get the cookies here (they will include the SMSESSION cookie)
                response = (HttpWebResponse)request.GetResponse();
                cookies.Add(response.Cookies);

                while (response.StatusCode == HttpStatusCode.Redirect)
                {

                    url = response.Headers[HttpResponseHeader.Location];

                    request = WebRequest.Create(url) as HttpWebRequest;
                    request.CookieContainer = cookies;
                    request.AllowAutoRedirect = false;

                    response = (HttpWebResponse)request.GetResponse();
                    cookies.Add(response.Cookies);

                }

                #region Manual Authenticate

                string rootUrl = string.Format("{0}://{1}", listUri.Scheme, listUri.Host);// "https://mydomain.com";
                request = WebRequest.Create(rootUrl) as HttpWebRequest;
                request.CookieContainer = cookies;
                request.AllowAutoRedirect = false;
                request.UserAgent = userAgent;
                response = (HttpWebResponse)request.GetResponse();
                cookies.Add(response.Cookies);

                while (response.StatusCode == HttpStatusCode.Redirect)
                {

                    url = response.Headers[HttpResponseHeader.Location];

                    if (url.StartsWith("/"))
                        url = rootUrl + url;

                    if (url.Contains("affwebservices"))
                        cookies.Add(jcookie); // This is the place we add the jsession cookie (seems optional)

                    request = WebRequest.Create(url) as HttpWebRequest;
                    request.CookieContainer = cookies;
                    request.UserAgent = userAgent;
                    request.AllowAutoRedirect = false;

                    response = (HttpWebResponse)request.GetResponse();
                    cookies.Add(response.Cookies);

                }
  
                #region Manual STS Post to Trust

                url = rootUrl + "/_trust/default.aspx";

                namevalues = GetHTMLInputTags(ResponseToString(response));

                string stringData = String.Format("wctx={0}&wresult={1}&wa=wsignin1.0",
                System.Web.HttpUtility.UrlEncode(namevalues["Wctx"]),
                System.Web.HttpUtility.UrlEncode(namevalues["wresult"].Replace(""", @"""")));

                request = HttpWebRequest.Create(url) as HttpWebRequest;

                Console.WriteLine(string.Format("Programmatic Post to URL = {0}
"
, request.RequestUri));
                Console.WriteLine(string.Format("Post Data = {0}

"
, stringData));

                request.Method = "POST";
                request.ContentType = "application/x-www-form-urlencoded";
                request.CookieContainer = cookies;
                request.UserAgent = userAgent;
                request.AllowAutoRedirect = false; // This is important
                Stream newStream = request.GetRequestStream();

                // Actually send the request
                using (StreamWriter sw = new StreamWriter(newStream))
                {
                    sw.Write(stringData); sw.Flush(); sw.Close();
                }

                response = request.GetResponse() as HttpWebResponse;
                //get the fed-auth cookie
                if (response.Cookies.Count > 0)
                {
                    var fedAuth = response.Cookies["FedAuth"];
                    if (fedAuth != null)
                    {
                        cookies.Add(fedAuth);
                    }
                    else
                    {
                        cookies.Add(response.Cookies);
                    }
                }
                else if (!string.IsNullOrEmpty(response.Headers[HttpResponseHeader.SetCookie]))
                {
                    string[] cookie = response.Headers[HttpResponseHeader.SetCookie].Split(';');

                    Cookie fedCookie = null;

                    for (int index = 0; index < cookie.Length; index++)
                    {
                        string[] cookieNameValue = cookie[index].Split('=');

                        switch (cookieNameValue[0].ToLower())
                        {
                            case "fedauth":
                                fedCookie = new Cookie(cookieNameValue[0], cookieNameValue[1]) { Domain = domain.Replace("https://", string.Empty) };
                                break;

                            case "expires":
                                fedCookie.Expires = Convert.ToDateTime(cookieNameValue[1]);
                                break;

                            case "path":
                                fedCookie.Path = cookieNameValue[1];
                                break;

                            case "secure":
                                fedCookie.Secure = true;
                                break;
                        }
                    }
                    cookies.Add(fedCookie);

                }
                else
                {
                    throw new ApplicationException("FedAuth cookie not received from Sharepoint. Please contact sharepoint administrator for further assistance.");
                }

                #endregion

                // Get the page we should hit next (should be Cognos)
                // Step 7: Persist the cookie
                while (response.StatusCode == HttpStatusCode.Redirect)
                {

                    url = response.Headers[HttpResponseHeader.Location];
                    Console.WriteLine(string.Format("Status = {0}, Response URL = {1}

"
, response.StatusDescription, url));

                    if (url.StartsWith("/"))
                        url = rootUrl + url;

                    request = WebRequest.Create(url) as HttpWebRequest;
                    request.CookieContainer = cookies;
                    request.AllowAutoRedirect = false;

                    Console.WriteLine(string.Format("Request URL = {0}
"
, request.RequestUri));

                    response = (HttpWebResponse)request.GetResponse();
                    cookies.Add(response.Cookies);

                }

                var lists = new Lists { CookieContainer = cookies };

                #region Code to update metadata for "My Submitted Content" list
                var camlcmd = new StringBuilder();
                camlcmd.Append("");
                foreach (var field in mmsParams.ListItemFields)
                {
                    camlcmd.AppendFormat
                        (
                            "{1}",
                            field.Key.Replace(" ", "_x0020_"),
                            field.Value
                        );
                }
                camlcmd.Append("
");

                var updates = (new XmlDocument()).CreateElement("Batch");
                updates.InnerXml = camlcmd.ToString();

                var resp = lists.UpdateListItems(mmsParams.PublishListName, updates);
                #endregion

                #region List Service response
                if (resp.FirstChild.FirstChild.InnerText == "0x00000000")
                {
                    var listitemid = resp.ChildNodes[0].ChildNodes[2].Attributes["ows_ID"].Value;
                    foreach (string attachment in mmsParams.Attachments)
                    {
                        lists.AddAttachment
                            (
                                mmsParams.PublishListName,
                                listitemid,
                                Path.GetFileName(attachment),
                                ReadBytes(attachment)
                            );
                    }
                }
                else
                {
                    throw new Exception("No field was found with that name. Check the name, and try again");
                }
                #endregion
            }
            finally
            {
                // Step 9: Delete the cookie from cache when done
                //DeletePersistentCookies(cookies);
            }
        }

        public static string ResponseToString(HttpWebResponse response)
        {
            // Get the stream containing content returned by the server and.
            // Open the stream using a StreamReader for easy access.
            using (StreamReader reader = new StreamReader(response.GetResponseStream()))
            {
                // Read the content.
                return reader.ReadToEnd();
            }
        }
        public static NameValueCollection GetHTMLInputTags(string responseString)
        {
            var htmlDocument = new HtmlDocument();
            htmlDocument.LoadHtml(responseString);
            var nodeCollection = htmlDocument.DocumentNode.SelectNodes("//input");
            var tags = new NameValueCollection();
            for (var index = 0; index < nodeCollection.Count; index++)
            {
                var attributeCollection = nodeCollection[index].Attributes;
                var type = attributeCollection["type"] != null ?
                                    attributeCollection["type"].Value :
                                    string.Empty;
                if (type.ToLower().Equals("submit") || type.ToLower().Equals("button")) continue;
                var name = attributeCollection["name"] != null
                                  ? attributeCollection["name"].Value
                                  : string.Empty;
                var value = attributeCollection["value"] != null
                                   ? attributeCollection["value"].Value
                                   : string.Empty;
                if (!string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(value))
                    tags.Add(name, value);
            }
            return tags;
        }

9 comments:

Anonymous said...

Hi, I think this is a very nice post but you missing this functions
can you post it?

GetHTMLInputTags(ResponseToString(response));

Hemant said...

Hi,
Sorry for the delayed response. Now I edited the post and added the missing 2 methods.

Thanks,
Hemant.

Sun said...

Hi Hemant,
I am working on a sharepoint 2010 project that is currently using active directory based authentication. Now we are migrating that to the Siteminder. I need some help from you about the development aspects of it. I didn't find any materials on the internet for the same.

I saw your blog and i hope you have some good experiance in Siteminder integration. If you have a any knowledge base or some best practices guide, could you please share that in your blog?

Also where to start in Siteminder integration with sharepoint would also help everyone.

Hemant said...

Hi Sun,

This resource on this subject is quite trickier or rather i would say tedious to find in the web but here are the resources I have used: Hope this helps you get upto the speed.

1. http://www.codeproject.com/Articles/80314/How-to-Connect-to-a-SiteMinder-Protected-Resource

2. http://msdn.microsoft.com/en-us/library/gg317440.aspx - On the left side you will see some example. You can review those to get good understanding about programmatic authentication.

As far as I know, the important thing to get hold of is the "FedAuth" cookie.

Thanks,
Hemant.

Rob said...

Is this code complete?

The first method contains an unclosed try without a catch statement, and the method itself unclosed.

Just wondering if there is more important stuff before the end of the try block.

Hemant said...

Yes Rob,
You are correct. Now I have updated the method. Thanks for pointing this out.

Regards,
Hemant.

Unknown said...

Hi Hemant,
I need to load the Sharepoint online page(https://mysite.sharepointonline.com)page into web browser control.

I am using ADFS to get authenticated FedAuth cookie.
How do I pass these cookies to load post login page into browser control.

Thanks for the help.

Hemant said...

Hi Vanibasu,
You mean you have the cookie and would like to pass that cookie to your web browser control post login page? If so, this is what i would try:

1. Attach navigate event to the web browser control and verify the page that is loaded. If the loaded page is not the login page, then please try the following:

webBrowser1.Document.Cookie = yourCookieFromADFS;

Thanks,
Hemant.

Hemant said...

Hi Vanibasu,

Check this link: http://www.twobitcoder.com/?p=256

Thanks,
Hemant.