Windows Authentication, Delegation, C#, and Exchange

You know, setting up an intranet to use windows authentication SHOULD be easy. And actually it is, you just set authentication="windows", impersonation to true, set up the browsers correctly so that they see the server as part of the intranet so it will automatically send the credentials (and God help you if you're using IE and the settings get corrupted. You'll have to reset everything to the factory defaults.), etc. It gets a bit more complicated though when you want to set up your intranet site such that people can access their exchange/outlook accounts. At that point you need to set up Kerberos on your intranet.

I'm not going to go over how to do that since this article describes it fairly well. There are a few things that you'll need to do if you're using a newer version of IIS though (namely set it up for constrained delegation, which just requires picking that option, finding your exchange server, and picking http from the list of services if you're going to be doing WebDAV calls against it). But to be honest, that's all there is to it... Well you might also need to set up the SPN for the intranet server properly (HOST/server name that people use to get to it). Because if that isn't set up, then the browser wont trust the server and it wont send the info... But that's about it.

Anyway, once you get past all of that you may want to do some simple queries against the Exchange server that you just spent a couple hours getting set up... And if you're using the code that I provide on my site (or potentially your own), most likely you'll run into an issue. Mainly the code I have (and that is used most often out there) wasn't set up for windows authentication (it assumed that you knew the user name and password). However there is an easy fix. The network credential cache needs to have a couple entries switched:

        System.Net.CredentialCache MyCredentialCache = new System.Net.CredentialCache();
        MyCredentialCache.Add(new System.Uri(uri)
                               "Negotiate",
                               (System.Net.NetworkCredential)CredentialCache.DefaultCredentials);

You'll notice two changes the second item when we're adding to the credential cache is normally NTLM. We've switched it to Negotiate (basically telling the system that we're going to be using Kerberos). The third item in the add function is no longer a new networkcredential object containing our user name and password. Instead it uses the default credentials. The reason for this is fairly simple. The DefaultCredentials contains the current user's information. That's all that needs to change in our bit of code. So hopefully this little bit of code will help someone out as it took me a bit to track down what my issue was (I didn't change from NTLM to Negotiate)...

Also, in other news I've moved my utility library over to CodePlex. I also ended up adding a few bits of code, including classes to help with:

  • Serialization
  • File management
  • HTML, added functions to dump request/response variables
  • XMDP
  • OPML
  • Active Directory queries
  • Exchange queries
  • iCalendar/Appointment management in Exchange
  • APML
  • hCalendar
  • hCard

Plus a couple of other bits here and there. I'm also trying to improve the structure a bit and try to make things a bit more logical. So hopefully it will help someone out. Anyway, try it out, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 11/14/2008 at 1:47 PM
Tags: , , , ,
Categories: AJAX | ASP.Net | C# | General | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Using WebDAV to Get Calendar Items (Appointments) from Exchange in C#/ASP.Net

I'm a big believer in integration of pretty much everything. Not that big on mash ups (as most of them are terrible) but I believe that information should be easily used in various systems. That's part of the reason that I like Microsoft products now (during the 90s, I was on the other end of the spectrum though so I'm happy with the changes they've been making). I especially like Exchange because it is extremely easy to pull information from it for your own purposes.

In my instance, I had to create a personalized calendar that pulled information from exchange and displayed it on an intranet page. With the help of WebDAV, it took about 10 minutes to write (and since people only come here for the code, I figured I'd share):

        public static List<VCalendar> GetCalendarItems(string UserName, string Password, string Directory,string Server,DateTime StartDate,DateTime EndDate)
        {
            try
            {
                List<VCalendar> ReturnArray = new List<VCalendar>();
                NetworkCredential credentials = new NetworkCredential(UserName, Password);
                string uri = string.Format("http://{0}/exchange/{1}/calendar/", Server, Directory);

                string Query = "<?xml version=\"1.0\"?>"
                     + "<g:searchrequest xmlns:g=\"DAV:\">"
                     + "<g:sql>SELECT \"urn:schemas:calendar:location\", \"urn:schemas:httpmail:subject\", "
                     + "\"urn:schemas:httpmail:htmldescription\", "
                     + "\"urn:schemas:calendar:dtstart\", \"urn:schemas:calendar:dtend\", "
                     + "FROM Scope('SHALLOW TRAVERSAL OF \"" + uri + "\"') "
                     + "WHERE \"urn:schemas:calendar:dtstart\" &gt;= '" + StartDate.ToString("yyyy/MM/dd hh:mm:ss") + "' "
                     + "AND \"urn:schemas:calendar:dtstart\" &lt;= '" + EndDate.ToString("yyyy/MM/dd hh:mm:ss") + "' "
                     + "ORDER BY \"urn:schemas:calendar:dtstart\" ASC"
                     + "</g:sql></g:searchrequest>";
                byte[] contents = System.Text.Encoding.UTF8.GetBytes(Query);

 

                HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
                request.Credentials = credentials;
                request.Method = "SEARCH";
                request.ContentLength = contents.Length;
                request.ContentType = "text/xml";

                using (System.IO.Stream requestStream = request.GetRequestStream())
                    requestStream.Write(contents, 0, contents.Length);

                using (HttpWebResponse response = request.GetResponse() as HttpWebResponse)
                {
                    using (System.IO.Stream responseStream = response.GetResponseStream())
                    {
                        XmlDocument document = new XmlDocument();
                        document.Load(responseStream);
                        foreach (XmlElement element in document.GetElementsByTagName("a:prop"))
                        {
                            VCalendar Cal = new VCalendar();
                            if (element["e:htmldescription"] != null)
                            {
                                Cal.Description = element["e:htmldescription"].InnerText;
                            }
                            if (element["e:subject"] != null)
                            {
                                Cal.Subject = element["e:subject"].InnerText;
                            }
                            if (element["d:location"] != null)
                            {
                                Cal.Location = element["d:location"].InnerText;
                            }
                            if (element["d:dtstart"] != null)
                            {
                                Cal.StartTime = DateTime.Parse(element["d:dtstart"].InnerText);
                            }
                            if (element["d:dtend"] != null)
                            {
                                Cal.EndTime = DateTime.Parse(element["d:dtend"].InnerText);
                            }
                            ReturnArray.Add(Cal);
                        }
                    }
                }
                return ReturnArray;
            }
            catch
            {
                return new List<VCalendar>();
            }
        }

So let's talk about the code a bit. First off, it's using my VCalendar code from my utility library. Well actually it's using a slightly different version that contains a GethCalendar function. I'll talk about hCalendar items in a different post though... So beyond that, you'll notice that there is a large section that sets up the Query string object. That's the actual WebDAV query. It's very similar to SQL but you call fields based on the schemas that were set up (I'm using the urn:schemas:calendar schema the most in this bit of code). We also ask it to only give us items within the start and end dates (you'll notice I use &gt; and &lt;. The reason for this is pretty simple... It's XML that we're sending. XML+ > or < = bad...).

After the query itself we simply set up an HttpWebRequest object with the proper user name/password, content (in bytes), etc. We make the call to the exchange server and it returns to us a nice big chunk of XML in return. If you want to look through the structure of what it returns, go ahead (it's very basic). But the items themselves are located within a:prop objects. So we just get the list of those items and iterate through grabbing the information that we want. That's all there is to the code really. Anyway, I hope this helps someone out. So give it a try, leave feedback, and happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 11/7/2008 at 9:53 AM
Tags: , , ,
Categories: ASP.Net | C# | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed

Getting the GAL from Exchange in C#

This is a short post for today but it does give you some rather interesting code (or at least hopefully you think so). Have you ever wanted to access your companies global address list from your Exchange server/Active Directory setup on your intranet site? Well you can, and it's actually very easy to accomplish:

        public static StringDictionary GetGAL(string UserName, string Password)
        {
            try
            {
                StringDictionary ReturnArray = new StringDictionary();
                DirectoryEntry deDirEntry = new DirectoryEntry("LDAP://server",
                                                                   UserName,
                                                                   Password,
                                                                   AuthenticationTypes.Secure);
                DirectorySearcher mySearcher = new DirectorySearcher(deDirEntry);
                mySearcher.PropertiesToLoad.Add("cn");
                mySearcher.PropertiesToLoad.Add("mail");

                string sFilter = String.Format("(&(mailnickname=*)(|(objectcategory=user)(objectcategory=group)))");

                mySearcher.Filter = sFilter;
                mySearcher.Sort.Direction = SortDirection.Ascending;
                mySearcher.Sort.PropertyName = "cn";
                mySearcher.PageSize = 1000;

                SearchResultCollection results;
                results = mySearcher.FindAll();

                foreach (SearchResult resEnt in results)
                {
                    ResultPropertyCollection propcoll = resEnt.Properties;
                    string Name = "";
                    string Mail = "";
                    foreach (string key in propcoll.PropertyNames)
                    {
                        if (key == "cn")
                        {
                            foreach (object values in propcoll[key])
                            {
                                Name = values.ToString();
                            }
                        }
                        else if (key == "mail")
                        {
                            foreach (object values in propcoll[key])
                            {
                                Mail = values.ToString();
                            }
                        }
                    }
                    if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(Mail))
                    {
                        ReturnArray.Add(Name, Mail);
                    }
                }
                return ReturnArray;
            }
            catch
            {
                return null;
            }
        }

That's all there is to it. That code logs into your Active Directory (note that you have to change the code to actually point to your server) using whatever username and password you supply. It then makes a query, asking for the user name and email address of every group and individual that also has a mail nickname, sorting that list alphabetically by the user name. It then puts the returned values into a StringDictionary. Anyway, take a look at the code, leave feedback, and as always happy coding.

kick it on DotNetKicks.com   Shout it
Digg It!DZone It!StumbleUponTechnoratiRedditDel.icio.usNewsVineFurlBlinkListEmail

Posted by: James Craig
Posted on: 5/12/2008 at 4:52 PM
Tags: ,
Categories: ASP.Net | Web Design
Post Information: Permalink | Comments (0) | Post RSSRSS comment feed