Thursday, August 16, 2012

One may search against Active Directory for a particular account with C#.

Here I began experimenting with Active Directory and I continue in kind here:

using System.Web.Mvc;
using ActiveDirectoryStuff.Helpers;
using ActiveDirectoryStuff.Models;
namespace ActiveDirectoryStuff.Controllers
{
   public class HomeController : Controller
   {
      public ActionResult Index()
      {
         
//the following line requires: <authentication mode="Windows"/> in Web.config
         string name = System.Web.HttpContext.Current.User.Identity.Name;
         ViewBag.Message = name;
         return View();
      }
      
      public ActionResult About()
      {
         return View();
      }
      
      public ActionResult Find(string id)
      {
         if (id.Contains(".")) return RedirectToAction("FindEmailAddress", "Home", new { id =
               id });
         if (id.Contains("\\")) id = id.Replace('\\', '@');
         return RedirectToAction("FindUserName", "Home", new { id = id });
      }
      
      public ActionResult FindEmailAddress(string id)
      {
         string filter = string.Format(MagicStringHelper.SearchFilterTemplate,
               MagicStringHelper.NameEmailMembershipsAndMoreByPosition(1),
               id.ToLower().Trim());
         UserDto userDto = UserDtoCreator.MakeUserDto(filter);
         return View("Find", userDto);
      }
      
      public ActionResult FindUserName(string id)
      {
         if (id.Contains("@")) id = id.Split("@".ToCharArray())[1];
         string filter = string.Format(MagicStringHelper.SearchFilterTemplate,
               MagicStringHelper.NameEmailMembershipsAndMoreByPosition(0),
               id.ToLower().Trim());
         UserDto userDto = UserDtoCreator.MakeUserDto(filter);
         return View("Find", userDto);
      }
   }
}

 
 

Before I explain what is going on, let me show off my two helper classes. The first one just manages magic strings:

using System.Collections.Generic;
namespace ActiveDirectoryStuff.Helpers
{
   public static class MagicStringHelper
   {
      public static string NameOfOurDomain = "aac.dva.va.gov";
      public static string SearchFilterTemplate = "(({0}={1}))";
      public static Dictionary<string,string> NameEmailMembershipsAndMore = new
            Dictionary<string,string>
                        {
                           {"samaccountname","Active Directory Id"},
                           {"mail","Email Address"},
                           {"memberof","Memberships"},
                           {"distinguishedname","Distinguished Name"},
                           {"givenName","First Name"},
                           {"initials","Initials"},
                           {"sn","Surname"},
                           {"objectcategory","Object Category"},
                           {"displayname","Display Name"},
                           {"telephonenumber","Telephone"},
                           {"streetaddress","Street Address"},
                           {"l","City"},
                           {"st","State"},
                           {"postalcode","Postal Code"},
                           {"physicaldeliveryofficename","Office Name"},
                           {"department","Department"},
                           {"description","Description"},
                           {"title","Title"},
                           {"company","Company"},
                           {"manager","Manager"},
                           {"IsAccountLocked","Locked Out?"},
                           {"LockOutTime","Time Of Shut Out"}
                        };
      public static string NameEmailMembershipsAndMoreByPosition(int position)
      {
         int counter = 0;
         foreach (string key in NameEmailMembershipsAndMore.Keys)
         {
            if (counter == position) return key;
            counter++;
         }
         return null;
      }
   }
}

 
 

And now for the stuff you care about! The second helper crawls Active Directory to populate getsetters on a DTO with details on an Active Directory account:

using System.Collections.Generic;
using System.DirectoryServices;
using System.DirectoryServices.ActiveDirectory;
using System.Linq;
using ActiveDirectoryStuff.Models;
namespace ActiveDirectoryStuff.Helpers
{
   public class UserDtoCreator
   {
      public static UserDto MakeUserDto(string filter)
      {
         UserDto userDto = CreateDto();
         DirectorySearcher directorySearcher = CreateSearcher();
         directorySearcher.Filter = filter;
         foreach (KeyValuePair<string,string> property in
               MagicStringHelper.NameEmailMembershipsAndMore)
               directorySearcher.PropertiesToLoad.Add(property.Key);
         SearchResult searchResult = directorySearcher.FindOne();
         foreach (KeyValuePair<string, string> property in
               MagicStringHelper.NameEmailMembershipsAndMore)
         {
            if (searchResult.Properties[property.Key].Count != 0)
            {
               if (property.Key ==
                     MagicStringHelper.NameEmailMembershipsAndMoreByPosition(2))
               {
                  int counter = 0;
                  int counterCeiling = searchResult.Properties[property.Key].Count;
                  while (counter < counterCeiling)
                  {
                     userDto.GroupMemberships.Add(searchResult.Properties[property.Key]
                           [counter].ToString());
                     counter++;
                  }
               } else {
                  string value = searchResult.Properties[property.Key][0].ToString();
                  if (value.Trim() != "" && value.Trim() != "0")
                        userDto.FlatDataPoints.Add(property.Value, value);
               }
            }
         }
         return userDto;
      }
      
      private static UserDto CreateDto()
      {
         UserDto userDto = new UserDto();
         userDto.FlatDataPoints = new Dictionary<string, string>();
         userDto.GroupMemberships = new List<string>();
         return userDto;
      }
      
      private static DirectorySearcher CreateSearcher()
      {
         Forest forest = Forest.GetCurrentForest();
         Domain domain = forest.Domains.Cast<Domain>().FirstOrDefault(d => d.Name ==
               MagicStringHelper.NameOfOurDomain);
         DirectoryEntry root = domain.GetDirectoryEntry();
         return new DirectorySearcher(root);
      }
   }
}

 
 

Alright, so what is going on here? Well, as you might guess I have a view model that I am populating. You may see what the DTO looks like in the next blob of code. It has two getsetters, a string/string dictionary and a list of string. I put the various details for a given Active Directory account into the dictionary using the display name as the key and the actual data as the value. There are a couple of exceptions to this rule. I do not add data points that are basically empty or set to zero, and I put the various details of group memberships into the list of string. Before I can do any of this I need to search against Active Directory for the particular individual to report on. In the CreateSearcher() method I grab the forest and then find my particular domain. We will only search against this domain. I ultimately return a DirectorySearcher. I then pass the DirectorySearcher a filter to pinpoint an individual. The filter will look like one of these:

  • ((samaccountname=vafscjaesct))
  • ((mail=tom.jaeschke@va.gov))

SearchResult searchResult = directorySearcher.FindOne(); will then execute the search using the filter. All that remains to be done going forward from this point is to pull data points from searchResult. They will be populated to my view model:

using System.Collections.Generic;
using System.Web.Mvc;
namespace ActiveDirectoryStuff.Models
{
   public class UserDto : Controller
   {
      public Dictionary<string, string> FlatDataPoints;
      public List<string> GroupMemberships;
   }
}

 
 

The view to display my view model:

@model ActiveDirectoryStuff.Models.UserDto
@{
   ViewBag.Title = "Specs";
}
<h2>About the User:</h2>
<table border="0">
   @foreach (KeyValuePair<string, string> flatDataPoint in Model.FlatDataPoints)
   {
      <tr>
         <td nowrap align="right">@flatDataPoint.Key</td>
         <td nowrap style="color: black;">@flatDataPoint.Value</td>
      </tr>
   }
      <tr>
         <td></td>
         <td nowrap>
            <h3>Groups</h3>
            @foreach (string group in Model.GroupMemberships)
            {
               <br />@group
            }
         </td>
      </tr>
</table>

 
 

There is an error with the Distinguished Name designation that I have fixed since taking this picture, but otherwise... the view will bring back something, more or less, like this:

Share photos on twitter with Twitpic

 
 

My Home View is unchanged since my prior blog posting.

@{
   ViewBag.Title = "Home Page";
}
<h2>Your Active Directory Account!</h2>
<p>
   <a href="/Home/Find/@ViewBag.Message.Replace('\\', '@')/">Click here</a> to learn
         more about: @ViewBag.Message
</p>

 
 

I did make a form at the About View to allow one to query for a user by Active Directory Id or email address:

@{
   ViewBag.Title = "About";
}
<h2>Find out about...</h2>
@using (Html.BeginForm("Find", "Home", FormMethod.Post, new { }))
{
   <input name="id"/>
   <button type="submit">Go</button>
}

No comments:

Post a Comment