I needed a way to unlock accounts easily while out of the office, so I wrote this ASP.Net application. It also allows you to enable and disable accounts, reset passwords, and view last logon time and the date the account expires. It should be trivial to add additional functionality. I use this in conjuction with the event log monitor application, which sends me an email whenever a user is locked out – I can immediately unlock it form my BlackBerry.
Download the code: ActiveDirectoryWebAdmin.zip 30.4 KB, MD5: a6be37b358367ddec5b6be4d6ab9604a
Configuration
You’ll need to modify the Web.config file to suit your organization. The settings are shown here:
<appSettings>
<add key="ApplicationName" value="Active Directory WebAdmin"/>
<add key="SearchQueryBase" value="(&(objectCategory=user)(!ou=Contacts)(|(samAccountName=*{0}*)(cn=*{0}*)))"/>
<add key="SearchQueryAll" value="(&(objectCategory=user)(!ou=Contacts))" />
<add key="SearchQuerySpecifcUsername" value="(&(objectCategory=user)(!ou=Contacts)(|(samAccountName={0})))"/>
<add key="AuthenticateAgainstGroup" value="YOURDOMAIN\Schema Admins" />
</appSettings>
<connectionStrings>
<add name="AdConnectionString" connectionString="LDAP://ExampleDomain.local/DC=ExampleDomain,DC=local" />
</connectionStrings>
The AdConnectionString setting should be changed to reflect your domain. By default the configuration is set to impersonate the user requesting the page. For someone to succesfully access the site, they have to be a member of the group entered in the AuthenticateAgainstGroup settings (I use the Schema Admins group, and this must be preceded by the domain name). If authentication fails, the AccessDenied.html file is shown. If you leave this setting blank, then there will be no authentication performed beyond what you have set up in IIS. If you use some other type of authentication, you may have to enable impersonation for a specific identity.
For the most part, you shouldn’t need to change any of the other settings. ApplicationName is what is used for the page titles. SearchQueryBase is the LDAP query used when someone enters text in the main search box. SearchQueryAll is the LDAP query that is run if someone attempts a blank search. For large organizations, it might be better to change this to something that doesn’t return any results. SearchQuerySpecificUsername is used to find a specific username.
Code
SharedBasePage
The SharedBasePage class is inherited by all other pages in the application. It serves two functions: authentication, and determining the approriate theme to use. Both of these occur in the page’s PreInit event.
Authentication, as described above, checks for membership in the class identified in the Web.config’s AuthenticateAgainstGroup setting. If that’s blank, the function simply returns true.
public static bool Authenticate()
{
string authGroup = ConfigurationManager.AppSettings["AuthenticateAgainstGroup"];
// if the "AuthenticateAgainstGroup settings isn't set, let IIS authenticate,
// otherwise we'll make sure the user is part of this group before we allow them
// to continue
if (string.IsNullOrEmpty(authGroup))
{
return true;
}
else
{
try
{
// Get the Windows Identity
WindowsIdentity wi = (WindowsIdentity)HttpContext.Current.User.Identity;
// Compare each group with the authenticate-against group
foreach (IdentityReference idRef in wi.Groups.Translate(typeof(NTAccount)))
{
if (idRef.Value.Equals(authGroup, StringComparison.CurrentCultureIgnoreCase))
return true;
}
}
catch { }
return false;
}
}
The SharedBasePage also tries to determine if a request is from a mobile browser, and sets the appropriate theme. It checks the Request.Browser.IsMobileDevice value, but only uses it if it returns true, because it’s really not a very good indicator otherwise. If it’s false, it will check for a “text/vnd.wap.wml” or “application/vnd.wap.xhtml+xml” value in the browsers AcceptTypes, which indicates that the browser supports WAP and should be treated as a mobile client. A cookie is set on the client at that point so that we can just check the cookie value on subsequent requests.
internal bool IsMobile
{
get
{
// See if we've already checked this and stored it in a cookie.
HttpCookie cookie = Request.Cookies["IsMobile"];
if (cookie == null)
{
// Create a new cookie so we don't have to go through this for future requests --
// we can just check the cookie value.
cookie = new HttpCookie("IsMobile","false");
if(Request.Browser.IsMobileDevice)
{
cookie.Value = "true";
}
else
{
// IsMobileDevice doesn't do a good job of recognizing all mobile devices,
// so we'll check the browser's AcceptTypes to see if it accepts WAP content.
foreach(string acceptType in Request.AcceptTypes)
{
if(acceptType.Equals("text/vnd.wap.wml") || // WAP 1.0
acceptType.Equals("application/vnd.wap.xhtml+xml")) // WAP 2.0
{
cookie.Value = "true";
break;
}
}
}
}
return cookie.Value.Equals("true", StringComparison.CurrentCultureIgnoreCase);
}
}
Interface
The UI is very minimal — mainly because I created this primarily to access from a mobile browser. There are only two pages that make up the application. Default.aspx performs user lookups and displays the results in a GridView control. If a search returns a single result, the user is automatically forwarded to the details page for that result. The User.aspx page displays the details for a specific user, and provides the ability to perform the different actions for an AD account.
AdHelper
The AdHelper class is what performs all of the interaction with Active Directory. It uses classes from the System.DirectoryServices namespace. The root entry is generated from the value in the Web.config, and is used for the main searches:
activeDirectoryRootEntry = new DirectoryEntry(ConfigurationManager.ConnectionStrings["AdConnectionString"].ConnectionString);
The searches return AdUserInfo objects. These contain an LdapPath property that is passed to the AdHelper methods that perform the various actions on a user account, such as unlocking an account:
public static void Unlock(string path)
{
try
{
DirectoryEntry entry = new DirectoryEntry(path);
entry.Properties["LockOutTime"].Value = 0;
entry.CommitChanges();
entry.Close();
}
catch (Exception ex)
{
throw new AdHelperException(ex.Message, ex);
}
}
One final note: The MustChangePasswordAtNextLogon method doesn’t work. I was unable to get this flag to work correctly without resetting the current password.
Twitter