293 lines
8.1 KiB
JavaScript
293 lines
8.1 KiB
JavaScript
|
|
const SyncConnector = require('./sync-connector');
|
|
const nthash = require('smbhash').nthash;
|
|
const ldap = require('./ldapjs-extra');
|
|
const crypto = require('crypto');
|
|
|
|
class SyncLdap extends SyncConnector {
|
|
async init() {
|
|
let ldapConfig = await this.$.LdapConfig.findOne({
|
|
fields: [
|
|
'server',
|
|
'rdn',
|
|
'password',
|
|
'userDn',
|
|
'groupDn'
|
|
]
|
|
});
|
|
if (!ldapConfig) return false;
|
|
|
|
let client = ldap.createClient({
|
|
url: ldapConfig.server
|
|
});
|
|
await client.bind(ldapConfig.rdn, ldapConfig.password);
|
|
|
|
Object.assign(this, {
|
|
ldapConfig,
|
|
client
|
|
});
|
|
return true;
|
|
}
|
|
|
|
async deinit() {
|
|
if (this.client)
|
|
await this.client.unbind();
|
|
}
|
|
|
|
async sync(info, userName, password) {
|
|
let {
|
|
ldapConfig,
|
|
client,
|
|
accountConfig
|
|
} = this;
|
|
|
|
let {user} = info;
|
|
|
|
let res = await client.search(ldapConfig.userDn, {
|
|
scope: 'sub',
|
|
attributes: ['userPassword', 'sambaNTPassword'],
|
|
filter: `&(uid=${userName})`
|
|
});
|
|
|
|
let oldUser;
|
|
await new Promise((resolve, reject) => {
|
|
res.on('error', reject);
|
|
res.on('searchEntry', e => oldUser = e.object);
|
|
res.on('end', resolve);
|
|
});
|
|
|
|
try {
|
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
|
await client.del(dn);
|
|
} catch (e) {
|
|
if (e.name !== 'NoSuchObjectError') throw e;
|
|
}
|
|
|
|
if (!info.hasAccount) {
|
|
if (oldUser)
|
|
console.log(` -> '${userName}' removed from LDAP`);
|
|
return;
|
|
}
|
|
|
|
let nickname = user.nickname || userName;
|
|
let nameArgs = nickname.trim().split(' ');
|
|
let sn = nameArgs.length > 1
|
|
? nameArgs.splice(1).join(' ')
|
|
: '-';
|
|
|
|
let dn = `uid=${userName},${ldapConfig.userDn}`;
|
|
let newEntry = {
|
|
uid: userName,
|
|
objectClass: [
|
|
'inetOrgPerson',
|
|
'posixAccount',
|
|
'sambaSamAccount'
|
|
],
|
|
cn: nickname,
|
|
displayName: nickname,
|
|
givenName: nameArgs[0],
|
|
sn,
|
|
mail: info.corporateMail,
|
|
preferredLanguage: user.lang,
|
|
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
|
loginShell: accountConfig.shell,
|
|
uidNumber: info.uidNumber,
|
|
gidNumber: accountConfig.idBase + user.roleFk,
|
|
sambaSID: '-'
|
|
};
|
|
|
|
if (password) {
|
|
let salt = crypto
|
|
.randomBytes(8)
|
|
.toString('base64');
|
|
|
|
let hash = crypto.createHash('sha1');
|
|
hash.update(password);
|
|
hash.update(salt, 'binary');
|
|
let digest = hash.digest('binary');
|
|
|
|
let ssha = Buffer
|
|
.from(digest + salt, 'binary')
|
|
.toString('base64');
|
|
|
|
Object.assign(newEntry, {
|
|
userPassword: `{SSHA}${ssha}`,
|
|
sambaNTPassword: nthash(password)
|
|
});
|
|
} else if (oldUser) {
|
|
Object.assign(newEntry, {
|
|
userPassword: oldUser.userPassword,
|
|
sambaNTPassword: oldUser.sambaNTPassword
|
|
});
|
|
}
|
|
|
|
for (let prop in newEntry) {
|
|
if (newEntry[prop] == null)
|
|
delete newEntry[prop];
|
|
}
|
|
|
|
await client.add(dn, newEntry);
|
|
}
|
|
|
|
async syncGroups(info, userName) {
|
|
let {
|
|
ldapConfig,
|
|
client
|
|
} = this;
|
|
|
|
let res = await client.search(ldapConfig.groupDn, {
|
|
scope: 'sub',
|
|
attributes: ['dn'],
|
|
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
|
});
|
|
|
|
let oldGroups = [];
|
|
await new Promise((resolve, reject) => {
|
|
res.on('error', reject);
|
|
res.on('searchEntry', e => oldGroups.push(e.object));
|
|
res.on('end', resolve);
|
|
});
|
|
|
|
let reqs = [];
|
|
for (let oldGroup of oldGroups) {
|
|
let change = new ldap.Change({
|
|
operation: 'delete',
|
|
modification: {memberUid: userName}
|
|
});
|
|
reqs.push(client.modify(oldGroup.dn, change));
|
|
}
|
|
await Promise.all(reqs);
|
|
|
|
if (!info.hasAccount) return;
|
|
|
|
reqs = [];
|
|
for (let role of info.user.roles()) {
|
|
let change = new ldap.Change({
|
|
operation: 'add',
|
|
modification: {memberUid: userName}
|
|
});
|
|
let roleName = role.inherits().name;
|
|
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
|
|
reqs.push(client.modify(dn, change));
|
|
}
|
|
await Promise.all(reqs);
|
|
}
|
|
|
|
async getUsers(usersToSync) {
|
|
let {
|
|
ldapConfig,
|
|
client
|
|
} = this;
|
|
|
|
let res = await client.search(ldapConfig.userDn, {
|
|
scope: 'sub',
|
|
attributes: ['uid'],
|
|
filter: `uid=*`
|
|
});
|
|
|
|
await new Promise((resolve, reject) => {
|
|
res.on('error', reject);
|
|
res.on('searchEntry', e => usersToSync.add(e.object.uid));
|
|
res.on('end', resolve);
|
|
});
|
|
}
|
|
|
|
async syncRoles() {
|
|
let {
|
|
$,
|
|
ldapConfig,
|
|
client,
|
|
accountConfig
|
|
} = this;
|
|
|
|
// Delete roles
|
|
|
|
let opts = {
|
|
scope: 'sub',
|
|
attributes: ['dn'],
|
|
filter: 'objectClass=posixGroup'
|
|
};
|
|
let res = await client.search(ldapConfig.groupDn, opts);
|
|
|
|
let reqs = [];
|
|
await new Promise((resolve, reject) => {
|
|
res.on('error', err => {
|
|
if (err.name === 'NoSuchObjectError')
|
|
err = new Error(`Object '${ldapConfig.groupDn}' does not exist`);
|
|
reject(err);
|
|
});
|
|
res.on('searchEntry', e => {
|
|
reqs.push(client.del(e.object.dn));
|
|
});
|
|
res.on('end', resolve);
|
|
});
|
|
await Promise.all(reqs);
|
|
|
|
// Recreate roles
|
|
|
|
let roles = await $.Role.find({
|
|
fields: ['id', 'name', 'description']
|
|
});
|
|
let roleRoles = await $.RoleRole.find({
|
|
fields: ['role', 'inheritsFrom']
|
|
});
|
|
let roleMap = toMap(roleRoles, e => {
|
|
return {key: e.inheritsFrom, val: e.role};
|
|
});
|
|
|
|
let accounts = await $.UserAccount.find({
|
|
fields: ['id'],
|
|
include: {
|
|
relation: 'user',
|
|
scope: {
|
|
fields: ['name', 'roleFk'],
|
|
where: {active: true}
|
|
}
|
|
}
|
|
});
|
|
let accountMap = toMap(accounts, e => {
|
|
let user = e.user();
|
|
if (!user) return;
|
|
return {key: user.roleFk, val: user.name};
|
|
});
|
|
|
|
reqs = [];
|
|
for (let role of roles) {
|
|
let newEntry = {
|
|
objectClass: ['top', 'posixGroup'],
|
|
cn: role.name,
|
|
description: role.description,
|
|
gidNumber: accountConfig.idBase + role.id
|
|
};
|
|
|
|
let memberUid = [];
|
|
for (let subrole of roleMap.get(role.id) || [])
|
|
memberUid = memberUid.concat(accountMap.get(subrole) || []);
|
|
|
|
if (memberUid.length) {
|
|
memberUid.sort((a, b) => a.localeCompare(b));
|
|
newEntry.memberUid = memberUid;
|
|
}
|
|
|
|
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
|
reqs.push(client.add(dn, newEntry));
|
|
}
|
|
await Promise.all(reqs);
|
|
}
|
|
}
|
|
|
|
SyncConnector.connectors.push(SyncLdap);
|
|
module.exports = SyncLdap;
|
|
|
|
function toMap(array, fn) {
|
|
let map = new Map();
|
|
for (let item of array) {
|
|
let keyVal = fn(item);
|
|
if (!keyVal) continue;
|
|
let key = keyVal.key;
|
|
if (!map.has(key)) map.set(key, []);
|
|
map.get(key).push(keyVal.val);
|
|
}
|
|
return map;
|
|
}
|