Skip to content

Instantly share code, notes, and snippets.

@mfcollins3
Created May 10, 2012 14:30
Show Gist options
  • Save mfcollins3/2653417 to your computer and use it in GitHub Desktop.
Save mfcollins3/2653417 to your computer and use it in GitHub Desktop.
Node.js application that spawns a .NET application to query Active Directory and return a user's profile

Active Directory Sample

The purpose of this gist is to demonstrate how to query an Active Directory server for a user's profile from a Node.js application. This sample assumes that you are running Node.js on the Microsoft Windows operating system and will not work for Linux or other platforms. The scenario demonstrated is an Express application running in iisnode with Windows Authentication enabled.

How it works:

  1. iisnode is configured to return the name of the logged in or impersonated user.
  2. The Node.js application uses a regular expression to split the domain name from the user name.
  3. The Node.js application spawns a child .NET process that will query Active Directory for the user profile and will output the user profile as a JSON object to standard output.
  4. The Node.js application will read the standard output stream and will parse the JSON object.
  5. The Node.js application will store the user information in the HTTP request object.

Things that you can do once you have the user profile:

  • Store the user information in your database so that you don't have to query Active Directory for it.
  • Store the user profile information in a cache so that you don't request Active Directory on every request (remember that you're running a child process and there will be a performance hit every time that it runs).
express = require('express');
routes = require('./routes');
spawn = require('child_process').spawn;
var app = module.exports = express.createServer();
app.configure(function () {
app.set('views', __dirname + '/views');
app.set('view engine', 'jade');
app.use(express.bodyParser());
app.use(express.methodOverride());
// custom middleware to query Active Directory
app.use(function (req, res, next) {
userName = req.headers['x-iisnode-logon_user'].match(/PUT-DOMAIN-NAME-HERE\\(.+)/)[1];
profileData = '';
aduserprofile = spawn('ADUserProfile.exe', [userName]);
aduserprofile.stdout.on('data', function (data) {
profileData = profileData + data;
});
aduserprofile.stderr.on('data', function (data) {
console.log("ADUserProfile: " + data);
});
aduserprofile.on('exit', function (code) {
userProfile = JSON.parse(profileData);
req/user = {
userName: userName,
displayName: userProfile.displayName
};
next();
});
});
app.use(app.router);
});
app.configure('development', function () {
app.use(express.errorHandler({dumpExceptions: true, showStack: true});
});
app.configure('production', function () {
app.use(express.errorHandler());
});
app.get('/', routes.index);
port = process.env.PORT || 3000;
app.listen(port, function () {
console.log("Express server listening on port ' + port + " in " + app.settings.env + " mode.");
});
namespace ADUserProfile
{
using System;
using System.DirectoryServices;
using System.Globalization;
using Newtonsoft.Json;
/// <summary>
/// Program that will query Active Directory for a user's
/// profile and will output the user's profile to standard output as a JSON
/// object.
/// </summary>
public static class Program
{
/// <summary>
/// Queries Active Directory for a user's profile and will
/// output the user's profile to standard output as a JSON object.
/// </summary>
/// <param name="args">
/// An array containing the command line arguments for the program.
/// </param>
/// <returns>
/// Returns zero if the user profile was successfully returned, or 1 if
/// the user was not found or another error occurred.
/// </returns>
public static int Main(string[] args)
{
if (1 != args.Length)
{
return 1;
}
try
{
var directoryEntry = new DirectoryEntry("LDAP://PUT-AD-SERVER-HERE/DC=...");
var directorySearcher = new DirectorySearcher(directoryEntry);
SearchResult searchResult;
using (directoryEntry)
using (directorySearcher)
{
directorySearcher.PropertiesToLoad.AddRange(new string[]
{
"displayname",
"givenname",
"sn",
"mail",
"mobile",
"telephonenumber"
});
directorySearcher.Filter = string.Format(
CultureInfo.InvariantCulture, "(SAMAccountName={0})", args[0]);
searchResult = directorySearcher.FindOne();
}
var userProfile = new UserProfile
{
DisplayName = GetPropertyValue(searchResult, "displayname"),
Email = GetPropertyValue(searchResult, "mail"),
GivenName = GetPropertyValue(searchResult, "givenname"),
MobileNumber = GetPropertyValue(searchResult, "mobile"),
Surname = GetPropertyValue(searchResult, "sn"),
TelephoneNumber = GetPropertyValue(searchResult, "telephoneNumber")
};
using (var jsonWriter = new JsonTextWriter(Console.Out))
{
var serializer = new JsonSerializer();
serializer.Serialize(jsonWriter, userProfile);
}
}
catch (Exception ex)
{
Console.Error.WriteLine(ex);
return 2;
}
return 0;
}
/// <summary>
/// Queries a <see cref="SearchResult"/> object for the value of a
/// property.
/// </summary>
/// <param name="searchResult">
/// The <see cref="SearchResult"/> object.
/// </param>
/// <param name="propertyName">
/// The name of the property to retrieve.
/// </param>
/// <returns>
/// Returns the value of the property, or null if the property was not
/// returned for the user.
/// </returns>
private static string GetPropertyValue(SearchResult searchResult, string propertyName)
{
var results = searchResult.Properties[propertyName];
return 0 < results.Count ? results[0].ToString() : null;
}
}
}
namespace ADUserProfile
{
using Newtonsoft.Json;
/// <summary>
/// Models the user's profile.
/// </summary>
public class UserProfile
{
/// <summary>
/// Gets or sets the user's display name.
/// </summary>
/// <value>
/// The value of this property is the user's display name.
/// </value>
[JsonProperty("displayName")]
public string DisplayName { get; set; }
/// <summary>
/// Gets or sets the user's email address.
/// </summary>
/// <value>
/// The value of this property is the user's email address.
/// </value>
[JsonProperty("email")]
public string Email { get; set; }
/// <summary>
/// Gets or sets the user's given name.
/// </summary>
/// <value>
/// The value of this property is the user's given name.
/// </value>
[JsonProperty("givenName")]
public string GivenName { get; set; }
/// <summary>
/// Gets or sets the user's mobile telephone number.
/// </summary>
/// <value>
/// The value of this property is the user's mobile telephone number.
/// </value>
[JsonProperty("mobileNumber")]
public string MobileNumber { get; set; }
/// <summary>
/// Gets or sets the user's surname.
/// </summary>
/// <value>
/// The value of this property is the user's surname.
/// </value>
[JsonProperty("surname")]
public string Surname { get; set; }
/// <summary>
/// Gets or sets the user's work telephone number.
/// </summary>
/// <value>
/// The value of this property is the user's work telephone number.
/// </value>
[JsonProperty("telephoneNumber")]
public string TelephoneNumber { get; set; }
}
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<add name="iisnode" path="app.js" verb="*" modules="iisnode"/>
</handlers>
<iisnode node_env="development"
promoteServerVars="LOGON_USER"/>
<rewrite>
<rules>
<rule name="LogFile" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^[a-zA-z0-9_\-]+\.js\.logs\/\d+\.txt$"/>
</rule>
<rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
<match url="^app.js\/debug[\/]?"/>
</rule>
<rule name="StaticContent">
<action type="Rewrite" url="public{REQUEST_URI}"/>
</rule>
<rule name="DynamicContent">
<conditions>
<add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
</conditions>
<action type="Rewrite" url="app.js"/>
</rule>
</rules>
</rewrite>
</system.webServer>
</configuration>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment