salix/modules/account/back/models/ldap-config.js

260 lines
7.9 KiB
JavaScript

const app = require('vn-loopback/server/server');
const ldap = require('../util/ldapjs-extra');
const crypto = require('crypto');
const nthash = require('smbhash').nthash;
module.exports = Self => {
Self.getSynchronizer = async function() {
return await Self.findOne({
fields: [
'server',
'rdn',
'password',
'userDn',
'groupDn'
]
});
};
Object.assign(Self.prototype, {
async init() {
this.client = ldap.createClient({
url: this.server
});
await this.client.bind(this.rdn, this.password);
},
async deinit() {
await this.client.unbind();
},
async syncUser(userName, info, password) {
let {
client,
accountConfig
} = this;
let newEntry;
if (info.hasAccount) {
let {user} = info;
let oldUser = await client.searchOne(this.userDn, {
scope: 'sub',
attributes: ['userPassword', 'sambaNTPassword'],
filter: `&(uid=${userName})`
});
let nickname = user.nickname || userName;
let nameArgs = nickname.trim().split(' ');
let sn = nameArgs.length > 1
? nameArgs.splice(1).join(' ')
: '-';
newEntry = {
uid: userName,
objectClass: [
'inetOrgPerson',
'posixAccount',
'sambaSamAccount'
],
cn: nickname,
displayName: nickname,
givenName: nameArgs[0],
sn,
mail: info.corporateMail,
preferredLanguage: user.lang || 'en',
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];
}
}
// Remove and recreate (if applicable) user
let dn = `uid=${userName},${this.userDn}`;
let operation;
try {
await client.del(dn);
operation = 'delete';
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;
}
if (info.hasAccount) {
await client.add(dn, newEntry);
operation = 'add';
}
if (operation === 'delete')
console.log(` -> User '${userName}' removed from LDAP`);
},
async syncUserGroups(userName, info) {
let {client} = this;
let opts = {
scope: 'sub',
attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
};
let oldGroups = await client.searchAll(this.groupDn, opts);
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},${this.groupDn}`;
reqs.push(client.modify(dn, change));
}
await Promise.all(reqs);
},
async getUsers(usersToSync) {
let {client} = this;
let opts = {
scope: 'sub',
attributes: ['uid'],
filter: `uid=*`
};
await client.searchForeach(this.userDn, opts,
o => usersToSync.add(o.uid));
},
async syncRoles() {
let $ = app.models;
let {
client,
accountConfig
} = this;
// Prepare data
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};
});
// Delete roles
let opts = {
scope: 'sub',
attributes: ['dn'],
filter: 'objectClass=posixGroup'
};
let reqs = [];
await client.searchForeach(this.groupDn, opts,
o => reqs.push(client.del(o.dn)));
await Promise.all(reqs);
// Recreate roles
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},${this.groupDn}`;
reqs.push(client.add(dn, newEntry));
}
await Promise.all(reqs);
}
});
};
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;
}