942 lines
19 KiB
JavaScript
942 lines
19 KiB
JavaScript
const { Client } = require('ldapts');
|
|
|
|
class ActiveDirectoryManager {
|
|
|
|
constructor({
|
|
url,
|
|
baseDN,
|
|
username,
|
|
password,
|
|
userAttributes = [],
|
|
groupAttributes = [],
|
|
computerAttributes = []
|
|
}) {
|
|
|
|
this.url = url;
|
|
this.baseDN = baseDN;
|
|
this.username = username;
|
|
this.password = password;
|
|
|
|
this.userAttributes = userAttributes;
|
|
this.groupAttributes = groupAttributes;
|
|
this.computerAttributes = computerAttributes;
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* CONNECTION
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async createClient() {
|
|
|
|
const client = new Client({
|
|
url: this.url,
|
|
timeout: 10000,
|
|
connectTimeout: 10000
|
|
});
|
|
|
|
await client.bind(this.username, this.password);
|
|
|
|
return client;
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* HELPERS
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
escape(value) {
|
|
return String(value).replace(/[*()\\]/g, '\\$&');
|
|
}
|
|
|
|
bufferToGuid(buffer) {
|
|
|
|
const hex = buffer.toString('hex');
|
|
|
|
return [
|
|
hex.substring(6, 8),
|
|
hex.substring(4, 6),
|
|
hex.substring(2, 4),
|
|
hex.substring(0, 2),
|
|
'-',
|
|
hex.substring(10, 12),
|
|
hex.substring(8, 10),
|
|
'-',
|
|
hex.substring(14, 16),
|
|
hex.substring(12, 14),
|
|
'-',
|
|
hex.substring(16, 20),
|
|
'-',
|
|
hex.substring(20)
|
|
].join('');
|
|
}
|
|
|
|
normalizeEntry(entry) {
|
|
|
|
const obj = {};
|
|
|
|
for (const [key, value] of Object.entries(entry)) {
|
|
|
|
let normalized = value;
|
|
|
|
// GUID konvertieren
|
|
if (key === 'objectGUID' && value instanceof Buffer) {
|
|
normalized = this.bufferToGuid(value);
|
|
}
|
|
|
|
// Arrays mit einem Wert -> String
|
|
else if (Array.isArray(value)) {
|
|
|
|
if (value.length === 0) {
|
|
normalized = null;
|
|
}
|
|
|
|
else if (value.length === 1) {
|
|
normalized = value[0];
|
|
}
|
|
}
|
|
|
|
obj[key] = normalized;
|
|
}
|
|
|
|
obj.ObjectSource_ID = 2;
|
|
|
|
return obj;
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* GENERIC LDAP SEARCH
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async ldapSearch({
|
|
baseDN = this.baseDN,
|
|
filter,
|
|
attributes = []
|
|
}) {
|
|
|
|
const client = await this.createClient();
|
|
|
|
try {
|
|
|
|
const { searchEntries } = await client.search(baseDN, {
|
|
scope: 'sub',
|
|
filter,
|
|
attributes,
|
|
paged: true
|
|
});
|
|
|
|
return searchEntries.map(entry =>
|
|
this.normalizeEntry(entry)
|
|
);
|
|
|
|
} finally {
|
|
await client.unbind();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* USERS
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async getUser(username, attributes = this.userAttributes) {
|
|
|
|
const safe = this.escape(username);
|
|
|
|
const filter =
|
|
`(&(objectCategory=person)(objectClass=user)` +
|
|
`(|(sAMAccountName=${safe})` +
|
|
`(mail=${safe})` +
|
|
`(cn=${safe})))`;
|
|
|
|
const users = await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
|
|
return users[0] || null;
|
|
}
|
|
|
|
async getUserDN(username) {
|
|
|
|
const user = await this.getUser(
|
|
username,
|
|
['distinguishedName']
|
|
);
|
|
|
|
return user?.distinguishedName || null;
|
|
}
|
|
|
|
async getAllUsers(attributes = this.userAttributes) {
|
|
|
|
const filter =
|
|
'(&(objectCategory=person)(objectClass=user))';
|
|
|
|
return await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
}
|
|
|
|
async findUsers(query, attributes = this.userAttributes) {
|
|
|
|
const safe = this.escape(query);
|
|
|
|
const filter =
|
|
`(&(objectClass=user)` +
|
|
`(|(cn=${safe})` +
|
|
`(sAMAccountName=${safe})` +
|
|
`(mail=${safe})` +
|
|
`(displayName=${safe})))`;
|
|
|
|
return await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* GROUPS
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async getGroup(groupName, attributes = this.groupAttributes) {
|
|
|
|
const safe = this.escape(groupName);
|
|
|
|
const filter =
|
|
`(&(objectClass=group)(cn=${safe}))`;
|
|
|
|
const groups = await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
|
|
return groups[0] || null;
|
|
}
|
|
|
|
async findGroups(query, attributes = this.groupAttributes) {
|
|
|
|
const safe = this.escape(query);
|
|
|
|
const filter =
|
|
`(&(objectClass=group)(cn=${safe}))`;
|
|
|
|
return await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
}
|
|
|
|
async getAllGroups({
|
|
attributes = this.groupAttributes,
|
|
includeMembers = false,
|
|
memberAttributes = this.userAttributes || [
|
|
'objectGUID',
|
|
'cn',
|
|
'sAMAccountName',
|
|
'mail',
|
|
'distinguishedName'
|
|
]
|
|
} = {}) {
|
|
|
|
const finalAttributes = [...new Set([
|
|
...attributes,
|
|
...(includeMembers ? ['member'] : [])
|
|
])];
|
|
|
|
const filter = '(objectClass=group)';
|
|
|
|
const groups = await this.ldapSearch({
|
|
filter,
|
|
attributes: finalAttributes
|
|
});
|
|
|
|
const results = [];
|
|
|
|
for (const group of groups) {
|
|
|
|
const result = {};
|
|
|
|
// Gruppenattribute übernehmen
|
|
for (const attr of attributes) {
|
|
result[attr] = group[attr] ?? null;
|
|
}
|
|
|
|
// Mitglieder laden
|
|
if (includeMembers) {
|
|
|
|
const rawMembers = Array.isArray(group.member)
|
|
? group.member
|
|
: group.member
|
|
? [group.member]
|
|
: [];
|
|
|
|
const members = [];
|
|
|
|
for (const memberDN of rawMembers) {
|
|
|
|
const memberResults = await this.ldapSearch({
|
|
baseDN: memberDN,
|
|
filter: '(objectClass=*)',
|
|
attributes: memberAttributes
|
|
});
|
|
|
|
if (memberResults.length > 0) {
|
|
|
|
const member = memberResults[0];
|
|
|
|
const normalizedMember = {};
|
|
|
|
for (const attr of memberAttributes) {
|
|
normalizedMember[attr] = member[attr] ?? null;
|
|
}
|
|
|
|
members.push(normalizedMember);
|
|
}
|
|
}
|
|
|
|
result.members = members;
|
|
}
|
|
|
|
results.push(result);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* COMPUTERS
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async getComputer(name, attributes = this.computerAttributes) {
|
|
|
|
const safe = this.escape(name);
|
|
|
|
const filter =
|
|
`(&(objectClass=computer)` +
|
|
`(|(cn=${safe})(dNSHostName=${safe})))`;
|
|
|
|
const results = await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
|
|
return results[0] || null;
|
|
}
|
|
|
|
async getComputers(attributes = this.computerAttributes) {
|
|
|
|
return await this.ldapSearch({
|
|
filter: '(objectClass=computer)',
|
|
attributes
|
|
});
|
|
}
|
|
|
|
async getComputersFromOU(
|
|
ouDn,
|
|
attributes = this.computerAttributes
|
|
) {
|
|
|
|
return await this.ldapSearch({
|
|
baseDN: ouDn,
|
|
filter: '(objectClass=computer)',
|
|
attributes
|
|
});
|
|
}
|
|
|
|
async findComputers(
|
|
query,
|
|
attributes = this.computerAttributes
|
|
) {
|
|
|
|
const safe = this.escape(query);
|
|
|
|
const filter =
|
|
`(&(objectClass=computer)` +
|
|
`(|(cn=${safe})(dNSHostName=${safe})))`;
|
|
|
|
return await this.ldapSearch({
|
|
filter,
|
|
attributes
|
|
});
|
|
}
|
|
|
|
/**
|
|
* -----------------------------------------------------
|
|
* GROUP MEMBERSHIP
|
|
* -----------------------------------------------------
|
|
*/
|
|
|
|
async isUserMemberOfDirect(username, groupName) {
|
|
|
|
const user = await this.getUser(
|
|
username,
|
|
['distinguishedName']
|
|
);
|
|
|
|
if (!user) {
|
|
return false;
|
|
}
|
|
|
|
const safeGroup = this.escape(groupName);
|
|
|
|
const filter =
|
|
`(&(objectClass=group)` +
|
|
`(cn=${safeGroup})` +
|
|
`(member=${user.distinguishedName}))`;
|
|
|
|
const results = await this.ldapSearch({
|
|
filter,
|
|
attributes: ['cn']
|
|
});
|
|
|
|
return results.length > 0;
|
|
}
|
|
|
|
async isUserMemberOfRecursive(
|
|
username,
|
|
groupName,
|
|
visited = new Set()
|
|
) {
|
|
|
|
const key = groupName.toLowerCase();
|
|
|
|
if (visited.has(key)) {
|
|
return false;
|
|
}
|
|
|
|
visited.add(key);
|
|
|
|
const direct =
|
|
await this.isUserMemberOfDirect(
|
|
username,
|
|
groupName
|
|
);
|
|
|
|
if (direct) {
|
|
return true;
|
|
}
|
|
|
|
const group = await this.getGroup(
|
|
groupName,
|
|
['member']
|
|
);
|
|
|
|
if (!group || !group.member) {
|
|
return false;
|
|
}
|
|
|
|
const members = Array.isArray(group.member)
|
|
? group.member
|
|
: [group.member];
|
|
|
|
for (const dn of members) {
|
|
|
|
const match = dn.match(/CN=([^,]+)/i);
|
|
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
|
|
const subGroupName = match[1];
|
|
|
|
const found =
|
|
await this.isUserMemberOfRecursive(
|
|
username,
|
|
subGroupName,
|
|
visited
|
|
);
|
|
|
|
if (found) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
async getGroupSubgroups(
|
|
groupName,
|
|
visited = new Set()
|
|
) {
|
|
|
|
const key = groupName.toLowerCase();
|
|
|
|
if (visited.has(key)) {
|
|
return [];
|
|
}
|
|
|
|
visited.add(key);
|
|
|
|
const group = await this.getGroup(
|
|
groupName,
|
|
['member']
|
|
);
|
|
|
|
if (!group || !group.member) {
|
|
return [];
|
|
}
|
|
|
|
const members = Array.isArray(group.member)
|
|
? group.member
|
|
: [group.member];
|
|
|
|
const results = [];
|
|
|
|
for (const dn of members) {
|
|
|
|
const match = dn.match(/CN=([^,]+)/i);
|
|
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
|
|
const subGroupName = match[1];
|
|
|
|
const subGroup = await this.getGroup(
|
|
subGroupName
|
|
).catch(() => null);
|
|
|
|
if (!subGroup) {
|
|
continue;
|
|
}
|
|
|
|
results.push(subGroup);
|
|
|
|
results.push(
|
|
...await this.getGroupSubgroups(
|
|
subGroupName,
|
|
visited
|
|
)
|
|
);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
async getGroupRecursive(
|
|
groupName,
|
|
visited = new Set()
|
|
) {
|
|
|
|
const key = groupName.toLowerCase();
|
|
|
|
if (visited.has(key)) {
|
|
return null;
|
|
}
|
|
|
|
visited.add(key);
|
|
|
|
const group = await this.getGroup(groupName);
|
|
|
|
if (!group) {
|
|
return null;
|
|
}
|
|
|
|
const result = {
|
|
...group,
|
|
subgroups: []
|
|
};
|
|
|
|
if (!group.member) {
|
|
return result;
|
|
}
|
|
|
|
const members = Array.isArray(group.member)
|
|
? group.member
|
|
: [group.member];
|
|
|
|
for (const dn of members) {
|
|
|
|
const match = dn.match(/CN=([^,]+)/i);
|
|
|
|
if (!match) {
|
|
continue;
|
|
}
|
|
|
|
const subGroupName = match[1];
|
|
|
|
const subTree =
|
|
await this.getGroupRecursive(
|
|
subGroupName,
|
|
visited
|
|
);
|
|
|
|
if (subTree) {
|
|
result.subgroups.push(subTree);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
|
|
module.exports = ActiveDirectoryManager;
|
|
|
|
// const { Client } = require('ldapts');
|
|
|
|
// class ActiveDirectoryManager {
|
|
|
|
// constructor({
|
|
// url,
|
|
// baseDN,
|
|
// username,
|
|
// password,
|
|
// userAttributes = [],
|
|
// groupAttributes = [],
|
|
// computerAttributes = []
|
|
// }) {
|
|
|
|
// this.url = url;
|
|
// this.baseDN = baseDN;
|
|
// this.username = username;
|
|
// this.password = password;
|
|
|
|
// this.userAttributes = userAttributes;
|
|
// this.groupAttributes = groupAttributes;
|
|
// this.computerAttributes = computerAttributes;
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * CONNECTION
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async createClient() {
|
|
|
|
// const client = new Client({
|
|
// url: this.url,
|
|
// timeout: 10000,
|
|
// connectTimeout: 10000
|
|
// });
|
|
|
|
// await client.bind(this.username, this.password);
|
|
|
|
// return client;
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * HELPERS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// escape(value) {
|
|
// return String(value).replace(/[*()\\]/g, '\\$&');
|
|
// }
|
|
|
|
// bufferToGuid(buffer) {
|
|
|
|
// const hex = buffer.toString('hex');
|
|
|
|
// return [
|
|
// hex.substring(6, 8),
|
|
// hex.substring(4, 6),
|
|
// hex.substring(2, 4),
|
|
// hex.substring(0, 2),
|
|
// '-',
|
|
// hex.substring(10, 12),
|
|
// hex.substring(8, 10),
|
|
// '-',
|
|
// hex.substring(14, 16),
|
|
// hex.substring(12, 14),
|
|
// '-',
|
|
// hex.substring(16, 20),
|
|
// '-',
|
|
// hex.substring(20)
|
|
// ].join('');
|
|
// }
|
|
|
|
// normalizeEntry(entry) {
|
|
|
|
// const obj = {};
|
|
|
|
// for (const [key, value] of Object.entries(entry)) {
|
|
|
|
// let normalized = value;
|
|
|
|
// // GUID konvertieren
|
|
// if (key === 'objectGUID' && value instanceof Buffer) {
|
|
// normalized = this.bufferToGuid(value);
|
|
// }
|
|
|
|
// // Arrays mit einem Wert -> String
|
|
// else if (Array.isArray(value)) {
|
|
|
|
// if (value.length === 0) {
|
|
// normalized = null;
|
|
// }
|
|
|
|
// else if (value.length === 1) {
|
|
// normalized = value[0];
|
|
// }
|
|
// }
|
|
|
|
// obj[key] = normalized;
|
|
// }
|
|
// obj['ObjectSource_ID'] = 2;
|
|
// return obj;
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * GENERIC SEARCH
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async ldapSearch({
|
|
// baseDN = this.baseDN,
|
|
// filter,
|
|
// attributes = []
|
|
// }) {
|
|
|
|
// const client = await this.createClient();
|
|
|
|
// try {
|
|
|
|
// const { searchEntries } = await client.search(baseDN, {
|
|
// scope: 'sub',
|
|
// filter,
|
|
// attributes,
|
|
// paged: true
|
|
// });
|
|
|
|
// return searchEntries.map(entry =>
|
|
// this.normalizeEntry(entry)
|
|
// );
|
|
|
|
// } finally {
|
|
// await client.unbind();
|
|
// }
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * USERS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async getUser(username, attributes = this.userAttributes) {
|
|
|
|
// const safe = this.escape(username);
|
|
|
|
// const filter =
|
|
// `(&(objectCategory=person)(objectClass=user)` +
|
|
// `(|(sAMAccountName=${safe})(mail=${safe})(cn=${safe})))`;
|
|
|
|
// const users = await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
|
|
// return users[0] || null;
|
|
// }
|
|
|
|
// async getAllUsers(attributes = [
|
|
// 'objectGUID',
|
|
// 'sAMAccountName',
|
|
// 'mail',
|
|
// 'givenName',
|
|
// 'sn',
|
|
// 'employeeID',
|
|
// 'title',
|
|
// 'department',
|
|
// 'streetAddress',
|
|
// 'telephoneNumber',
|
|
// 'physicalDeliveryOfficeName',
|
|
// 'distinguishedName'
|
|
// ]) {
|
|
|
|
// const filter =
|
|
// '(&(objectCategory=person)(objectClass=user))';
|
|
|
|
// return await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
// }
|
|
|
|
// async findUsers(query, attributes = this.userAttributes) {
|
|
|
|
// const safe = this.escape(query);
|
|
|
|
// const filter =
|
|
// `(&(objectClass=user)` +
|
|
// `(|(cn=${safe})` +
|
|
// `(sAMAccountName=${safe})` +
|
|
// `(mail=${safe})` +
|
|
// `(displayName=${safe})))`;
|
|
|
|
// return await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * GROUPS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async getGroup(groupName, attributes = this.groupAttributes) {
|
|
|
|
// const safe = this.escape(groupName);
|
|
|
|
// const filter =
|
|
// `(&(objectClass=group)(cn=${safe}))`;
|
|
|
|
// const groups = await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
|
|
// return groups[0] || null;
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * ALL GROUPS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * ALL GROUPS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async getAllGroups({ attributes = this.groupAttributes, includeMembers = false } = {}) {
|
|
|
|
// const finalAttributes = [...new Set([
|
|
// ...attributes,
|
|
// ...(includeMembers ? ['member'] : [])
|
|
// ])];
|
|
|
|
// const filter = '(objectClass=group)';
|
|
|
|
// const groups = await this.ldapSearch({
|
|
// filter,
|
|
// attributes: finalAttributes
|
|
// });
|
|
|
|
// return groups.map(group => {
|
|
|
|
// const result = {};
|
|
|
|
// // Nur definierte Attribute übernehmen
|
|
// for (const attr of attributes) {
|
|
// result[attr] = group[attr] ?? null;
|
|
// }
|
|
// result['ObjectSource_ID'] = 2;
|
|
|
|
// // Optional Mitglieder hinzufügen
|
|
// if (includeMembers) {
|
|
|
|
// const rawMembers = Array.isArray(group.member)
|
|
// ? group.member
|
|
// : group.member
|
|
// ? [group.member]
|
|
// : [];
|
|
|
|
// result.members = rawMembers.map(memberDN => {
|
|
|
|
// const match = memberDN.match(/CN=([^,]+)/i);
|
|
|
|
// return {
|
|
// distinguishedName: memberDN,
|
|
// cn: match ? match[1] : null
|
|
// };
|
|
// });
|
|
// }
|
|
|
|
// return result;
|
|
// });
|
|
// }
|
|
|
|
// async findGroups(query, attributes = this.groupAttributes) {
|
|
|
|
// const safe = this.escape(query);
|
|
|
|
// const filter =
|
|
// `(&(objectClass=group)(cn=${safe}))`;
|
|
|
|
// return await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * COMPUTERS
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async getComputers(attributes = this.computerAttributes) {
|
|
|
|
// return await this.ldapSearch({
|
|
// filter: '(objectClass=computer)',
|
|
// attributes
|
|
// });
|
|
// }
|
|
|
|
// async getComputer(name, attributes = this.computerAttributes) {
|
|
|
|
// const safe = this.escape(name);
|
|
|
|
// const filter =
|
|
// `(&(objectClass=computer)` +
|
|
// `(|(cn=${safe})(dNSHostName=${safe})))`;
|
|
|
|
// const results = await this.ldapSearch({
|
|
// filter,
|
|
// attributes
|
|
// });
|
|
|
|
// return results[0] || null;
|
|
// }
|
|
|
|
// /**
|
|
// * -----------------------------------------------------
|
|
// * GROUP MEMBERSHIP
|
|
// * -----------------------------------------------------
|
|
// */
|
|
|
|
// async isUserMemberOfDirect(username, groupName) {
|
|
|
|
// const user = await this.getUser(username, [
|
|
// 'distinguishedName'
|
|
// ]);
|
|
|
|
// if (!user) {
|
|
// return false;
|
|
// }
|
|
|
|
// const safeGroup = this.escape(groupName);
|
|
|
|
// const filter =
|
|
// `(&(objectClass=group)` +
|
|
// `(cn=${safeGroup})` +
|
|
// `(member=${user.distinguishedName}))`;
|
|
|
|
// const results = await this.ldapSearch({
|
|
// filter,
|
|
// attributes: ['cn']
|
|
// });
|
|
|
|
// return results.length > 0;
|
|
// }
|
|
// }
|
|
|
|
// module.exports = ActiveDirectoryManager;
|