jsumners / node-activedirectory

ActiveDirectory is an Node.js ldapjs client for authN (authentication) and authZ (authorization) for Microsoft Active Directory with range retrieval support for large Active Directory installations. Originally forked from gheeres/node-activedirectory.
MIT License
52 stars 43 forks source link

ActiveDirectory for Node

Module Maintenance Notice

This module is minimally maintained. I, James Sumners, have not been in an environment where I need to communicate with an Active Directory server in over two years (as of this writing on 2020-08-25). Given that I do not need this module for my own work, it is not a priority for me. I will continue to review pull requests and issue releases as necessary, but to get the "best" support possible:

  1. Read through the module's source code to learn how to either resolve your issue or to point out where the issue is.
  2. Open a Pull Request to fix the issue if you are able. If you're not able, you should state why in any issue you open.
  3. Provide a thorough replication of your problem if you are opening an issue and not a Pull Request.

If you rely on this module, it would be a great idea to start contributing your own Pull Requests to resolve open issues or any issues you have encountered. If I determine that you would be a good steward of the project, I will invite you to take up full maintenance of it.

ActiveDirectory is an ldapjs client for authN (authentication) and authZ (authorization) for Microsoft Active Directory with range retrieval support for large Active Directory installations. This code was a port of an existing C# library (not published) that I had written a few years ago. Here are the key features

Installation

npm install activedirectory2

Usage

var ActiveDirectory = require('activedirectory2');
var config = { url: 'ldap://dc.domain.com',
               baseDN: 'dc=domain,dc=com',
               username: 'username@domain.com',
               password: 'password' }
var ad = new ActiveDirectory(config);

The username and password specified in the configuration are what are used for user and group lookup operations.

Documentation

A Promise wrapper is available for all methods by an alternate require statement:

const AD = require('activedirectory2').promiseWrapper;
const config = { url: 'ldap://dc.domain.com',
               baseDN: 'dc=domain,dc=com',
               username: 'username@domain.com',
               password: 'password' }
const ad = new AD(config);

ActiveDirectory(options)

Returns a new instance of the client configured according to the options object.

Options:

Any additional keys on the options object will be parsed to find ldapjs options. The list of options activedirectory2 parses as ldapjs options is:


authenticate(username, password, callback)

Authenticates the username and password by doing a simple bind with the specified credentials.

Arguments

Example

var ad = new ActiveDirectory(config);
var username = 'john.smith@domain.com';
var password = 'password';

ad.authenticate(username, password, function(err, auth) {
  if (err) {
    console.log('ERROR: '+JSON.stringify(err));
    return;
  }

  if (auth) {
    console.log('Authenticated!');
  }
  else {
    console.log('Authentication failed!');
  }
});

isUserMemberOf(opts, username, groupName, callback)

Checks to see if a user is a member of the specified group. This function will also check for group membership inside of a group. Even if a user is not explicity listed as a member of a particular group, if a group that the user is a member of belongs to the group, then this function will return true.

Arguments

Example

var username = 'user@domain.com';
var groupName = 'Employees';

var ad = new ActiveDirectory(config);
var ad.isUserMemberOf(username, groupName, function(err, isMember) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(username + ' isMemberOf ' + groupName + ': ' + isMember);
});

groupExists(opts, groupName, callback)

Checks to see if the specified group exists.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.groupExists(groupName, function(err, exists) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(groupName + ' exists: ' + exists);
});

userExists(opts, username, callback)

Checks to see if the specified user exists.

Arguments

Example

var username = 'john.smith@domain.com';

var ad = new ActiveDirectory(config);
ad.userExists(username, function(err, exists) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log(username + ' exists: ' + exists);
});

getUsersForGroup(opts, groupName, callback)

For the specified group, retrieve all of the users that belong to the group. If the group contains groups, then the members of those groups are recursively retrieved as well to build a complete list of users that belong to the specified group.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.getUsersForGroup(groupName, function(err, users) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! users) console.log('Group: ' + groupName + ' not found.');
  else {
    console.log(JSON.stringify(users));
  }
});

getGroupMembershipForUser(opts, username, callback)

For the specified username, retrieve all of the groups that a user belongs to. If a retrieved group is a member of another group, then that group is recursively retrieved as well to build a complete hierarchy of groups that a user belongs to.

Arguments

Example

var sAMAccountName = 'john.smith@domain.com';

var ad = new ActiveDirectory(config);
ad.getGroupMembershipForUser(sAMAccountName, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! groups) console.log('User: ' + sAMAccountName + ' not found.');
  else console.log(JSON.stringify(groups));
});

getGroupMembershipForGroup(opts, groupName, callback)

For the specified group, retrieve all of the groups that the group is a member of. If a retrieved group is a member of another group, then that group is recursively retrieved as well to build a complete hierarchy of groups that a user belongs to.

Arguments

Example

var groupName = 'Employees';

var ad = new ActiveDirectory(config);
ad.getGroupMembershipForGroup(groupName, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! groups) console.log('Group: ' + groupName + ' not found.');
  else console.log(JSON.stringify(groups));
});

find(opts, callback)

Perform a generic search for the specified LDAP query filter. This function will return both groups and users that match the specified filter. Any results not recognized as a user or group (i.e. computer accounts, etc.) can be found in the 'other' attribute / array of the result.

Arguments

Example

var _ = require('underscore');
var query = 'cn=*Exchange*';
var opts = {
  includeMembership : [ 'group', 'user' ], // Optionally can use 'all'
  includeDeleted : false
};

var ad = new ActiveDirectory(config);
ad.find(query, function(err, results) {
  if ((err) || (! results)) {
    console.log('ERROR: ' + JSON.stringify(err));
    return;
  }

  console.log('Groups');
  _.each(results.groups, function(group) {
    console.log('  ' + group.cn);
  });

  console.log('Users');
  _.each(results.users, function(user) {
    console.log('  ' + user.cn);
  });

  console.log('Other');
  _.each(results.other, function(other) {
    console.log('  ' + other.cn);
  });
});

findDeletedObjects(opts, callback)

If tombstoning (recycle bin) is enabled for the Active Directory installation, use findDeletedObjects to retrieve items in the recycle bin.

More information about tombstoning and enabling can be found at:

Note: That when an LDAP entry / object is tombstoned, not all attributes for that item are retained. This is a limitation of Active Directory itself and not the library itself.

Arguments

If the baseDN is not specified, then a RootDSE query will be performed on the attached URL and 'ou-Deleted Objects' will be appended.

Example

var url = 'ldap://yourdomain.com';
var opts = {
  baseDN: 'ou=Deleted Objects, dc=yourdomain, dc=com',
  filter: 'cn=*Bob*'
};
ad.findDeletedObjects(opts, function(err, result) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log('findDeletedObjects: '+JSON.stringify(result));
});

findUser(opts, username, callback)

Looks up or finds a username by their sAMAccountName, userPrincipalName, distinguishedName (dn) or custom filter. If found, the returned object contains all of the requested attributes. By default, the following attributes are returned:

Arguments

Example

// Any of the following username types can be searched on
var sAMAccountName = 'username';
var userPrincipalName = 'username@domain.com';
var dn = 'CN=Smith\\, John,OU=Users,DC=domain,DC=com';

// Find user by a sAMAccountName
var ad = new ActiveDirectory(config);
ad.findUser(sAMAccountName, function(err, user) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! user) console.log('User: ' + sAMAccountName + ' not found.');
  else console.log(JSON.stringify(user));
});

findUsers(opts, callback)

Perform a generic search for users that match the specified filter. The default LDAP filter for users is specified as (&(|(objectClass=user)(objectClass=person))(!(objectClass=computer))(!(objectClass=group)))

Arguments

Example

var query = 'cn=*George*';

var ad = new ActiveDirectory(config);
ad.findUsers(query, true, function(err, users) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if ((! users) || (users.length == 0)) console.log('No users found.');
  else {
    console.log('findUsers: '+JSON.stringify(users));
  }
});

findGroup(opts, groupName, callback)

Looks up or find a group by common name (CN) which is required to be unique in Active Directory or optionally by the distinguished name. Supports groups with range retrieval specifiers. The following attributes are returned by default for the group:

Arguments

Example

// Any of the following group names can be searched on
var groupName = 'Employees';
var dn = 'CN=Employees,OU=Groups,DC=domain,DC=com'

// Find group by common name
var ad = new ActiveDirectory(config);
ad.findGroup(groupName, function(err, group) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if (! user) console.log('Group: ' + groupName + ' not found.');
  else {
    console.log(group);
    console.log('Members: ' + (group.member || []).length);
  }
});

findGroups(opts, callback)

Perform a generic search for groups that match the specified filter. The default LDAP filter for groups is specified as (&(objectClass=group)(!(objectClass=computer))(!(objectClass=user))(!(objectClass=person)))

Arguments

Example

var query = 'CN=*Admin*';

var ad = new ActiveDirectory(config);
ad.findGroups(query, function(err, groups) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  if ((! groups) || (groups.length == 0)) console.log('No groups found.');
  else {
    console.log('findGroups: '+JSON.stringify(groups));
  }
});

getRootDSE(url, attributes, callback)

Retrieves the root DSE for the specified url. Can be called statically.

Arguments

Example

var url = 'ldap://yourdomain.com';
ActiveDirectory.prototype.getRootDSE(url, [ 'defaultNamingContext' ], function(err, result) {
  if (err) {
    console.log('ERROR: ' +JSON.stringify(err));
    return;
  }

  console.log('getRootDSE: '+JSON.stringify(result));
});

// Or can be called with an instance...
var ad = new ActiveDirectory(config);
ad.getRootDSE(function(err, result) {
  //...
});

Advanced Usage

Attributes

By default, the following attributes are returned for users and groups:

If you need to override those defaults, then you can override them when you create your ActiveDirectory instance:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: {
                                 user: [ 'myCustomAttribute', 'mail', 'userPrinicipalName' ],
                                 group: [ 'anotherCustomAttribute', 'objectCategory' ]
                               }
                              });

If overriding the 'user' or 'group' attribute, you must specify ALL of the attributes you want. The existing defaults will be overridden. Optionally, you can override the attributes on a per call basis using the 'opts' parameter.

Referrals

By default, referral chasing is disabled. To enable it, specify a referrals attribute when you create your instance. The referrals object has the following syntax:

{
  referrals: {
    enabled: false,
    excluded: [
      'ldaps?://ForestDnsZones\./.*',
      'ldaps?://DomainDnsZones\./.*',
      'ldaps?://.*/CN=Configuration,.*'
    ]
  }
}

The 'excluded' options is a list of regular expression filters to ignore specific referrals. The default exclusion list is included above, ignoring the special partitions that ActiveDirectory creates by default. To specify these options, override them as follows:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: { ... },
                               referrals: {
                                 enabled: true,
                                 excluded: [ ]
                               }
                              });

If you enable referral chasing, the specified username MUST be a userPrincipalName.

Custom Entry Parsing

if you want to manipulate the search entry in a different way or perhaps augment the search result with additional data, you can pass a custom parser. This is useful, for example, in case you want to change the objectSid or GUID which are binary values.

Example:

function customEntryParser(entry, raw, callback){
    if (raw.hasOwnProperty("objectSid")){
        entry.objectSid = raw.objectSid;
    }
    if (raw.hasOwnProperty("objectGUID")){
        entry.objectGUID = raw.objectGUID;
    }
    callback(entry);
};

If you want to specify your own parser you can override the default parser as follows:

var ad = new ActiveDirectory({ url: 'ldap://dc.domain.com',
                               baseDN: 'dc=domain,dc=com',
                               username: 'username@domain.com',
                               password: 'password',
                               attributes: { ... },
                               referrals: { ... },
                               entryParser : customEntryParser
                              });

Optionally, you can specify your custom entry parser as part of the 'opts' object. See optional parameters for more information.

var opts = function(entry, raw, callback) {
  entry.retrievedAt = new Date();
  callback(entry);
};
ad.findUser(opts, 'userPrincipalName=bob@domain.com', function(err, user) {
 ...
});

Optional Parameters / Extended Functionality

Any method which takes an opts parameter allows for additional options. Options for both activedirectory2 and the internal ldapjs client are supported.

Currently supported ldapjs options are:

activedirectory2 specific options are:

Example

var opts = {
  scope: 'sub',
  filter: 'objectClass=User',
  includeMembership: [ 'user' ],
  entryParser: function(entry, raw, callback) {
    // returning null with exclude result
    if (entry.ignore) return(null);

    entry.retrievedAt = new Date();
    entry.preferredServer = getPreferredServerFromDatabase(entry.userPrincipalName);

    callback(entry);
  }
};