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;
        }

Friday, January 13, 2012

How to: Force windows forms to appear on top of Setup Project window

Visual Studio is shipped with deployment project that us to create Setup package of their project to be installed in the client’s computer. Most of the time (for simple project requirements), we are good with the options available at the disposal but at times when it comes to more complex user interaction requirement during setup, we have to build our custom windows form to collect required details and then make use of it during the installation.

 

We faced a similar situation and we managed to build our custom form for the setup. The problem we faced is that our custom window is not showing on top of the setup project window. We have tried the following options but none helped.

 

Option 1 //no success

 

myForm.TopMost = True

 

Option 2 //no success

 

myForm.BringToFront();

myForm.Focus();

 

Then we googled and came across the following link: http://social.msdn.microsoft.com/Forums/en-US/winformssetup/thread/bbe69f12-8908-4c65-aa89-1963720d4c11/

Here we got a clue to set the setup window to be the parent of our custom window and also the code to do so as shown below:

 

Step 1: Create the following class in our setup project

 

public class WindowWrapper : System.Windows.Forms.IWin32Window

    {

        public WindowWrapper(IntPtr handle)

        {

            _hwnd = handle;

        }

 

        public IntPtr Handle

        {

            get { return _hwnd; }

        }

 

        private IntPtr _hwnd;

    }

 

Step 2: Just before we showed our custom window, we added the following code:

 

       IntPtr hwnd = IntPtr.Zero;

        WindowWrapper wrapper = null;

 

        Process[] procs = Process.GetProcessesByName("msiexec");

       

        if (null != procs && procs.Length > 0)       

            hwnd = procs[0].MainWindowHandle;

       

        if(hwnd != IntPtr.Zero)

            wrapper = new WindowWrapper(hwnd);

 

        //Set the windows forms owner to setup project so it can be focused and

        //set infront

        if (null != wrapper)

            myForm.ShowDialog(wrapper);

        else

            //incase the operation failed, no need to fail the whole process, so you can open the form normally

            myForm.ShowDialog();

 

Now, we built our setup project and were happy to see our custom window showing on top. But wait, we were seeing intermittent behaviours that sometimes our custom window is not getting showed on top. Then after closely looking at the processes loaded, we saw 5 msiexec processes running. What??? We were wondering why 5 when we are running only one setup! Then we looked at the Step 2 code and we found that we were always picking up first process among the matching processes no matter how many are there in the procs collection. We then tweaked the Step 2 code to create the WindowWrapper instance as follows:

 

if (null != procs && procs.Length > 0)       

            for (int index = 0; index < procs.Length; index++)

            {

                hwnd = procs[index].MainWindowHandle;

       

                if(hwnd != IntPtr.Zero)

                {

                    wrapper = new WindowWrapper(hwnd);

                    break;

                }

            }

 

Now we built the project again and were ready to run our setup package having our fingers crossed. Bingo! We were getting our custom window on top. We tried a couple more times to make sure and it was now always showing on top. Hope this may help someone.

 

Monday, January 09, 2012

"How Do I?" Videos for Visual C#

Hi Techies,

Here is the link to the latest series of “How Do I?” videos for Visual C#

Saturday, January 07, 2012

Bulk-Creating PDF Files from Word

To convert a whole folder full of MS Word documents to PDF, here's a function that might help:

function Export-WordToPDF {

  param(

  [Parameter(Mandatory=$true,ValueFromPipelineByPropertyName=$true)]

  [Alias("FullName")]

  $path,

  $pdfpath = $null)

 

  process {

    if (!$pdfpath) {

      $pdfpath = [System.IO.Path]::ChangeExtension($path, '.pdf')

    }

    $word = New-Object -ComObject Word.Application

    $word.displayAlerts = $false

   

    $word.Visible = $true

    $doc = $word.Documents.Open($path)

    #$doc.TrackRevisions = $false

    $null = $word.ActiveDocument.ExportAsFixedFormat($pdfpath, 17, $false, 1)

    $word.ActiveDocument.Close()

    $word.Quit()

  }

}

Use it like this:

PS> Dir c:\folder -Filter *.doc | Export-WordToPDF