2020-11-12 22:20:25 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
let newEntry;
|
2020-11-12 22:20:25 +00:00
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
if (info.hasAccount) {
|
|
|
|
let {user} = info;
|
2020-11-12 22:20:25 +00:00
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
let oldUser = await client.searchOne(this.userDn, {
|
|
|
|
scope: 'sub',
|
|
|
|
attributes: ['userPassword', 'sambaNTPassword'],
|
|
|
|
filter: `&(uid=${userName})`
|
|
|
|
});
|
2020-11-12 22:20:25 +00:00
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
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
|
|
|
|
});
|
|
|
|
}
|
2020-11-12 22:20:25 +00:00
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
for (let prop in newEntry) {
|
|
|
|
if (newEntry[prop] == null)
|
|
|
|
delete newEntry[prop];
|
|
|
|
}
|
2020-11-12 22:20:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
// Remove and recreate (if applicable) user
|
2020-11-12 22:20:25 +00:00
|
|
|
|
|
|
|
let dn = `uid=${userName},${this.userDn}`;
|
2020-11-13 12:20:37 +00:00
|
|
|
let operation;
|
2020-11-12 22:20:25 +00:00
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
try {
|
|
|
|
await client.del(dn);
|
|
|
|
operation = 'delete';
|
|
|
|
} catch (e) {
|
|
|
|
if (e.name !== 'NoSuchObjectError') throw e;
|
2020-11-12 22:20:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
if (info.hasAccount) {
|
|
|
|
await client.add(dn, newEntry);
|
|
|
|
operation = 'add';
|
2020-11-12 22:20:25 +00:00
|
|
|
}
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
if (operation === 'delete')
|
|
|
|
console.log(` -> User '${userName}' removed from LDAP`);
|
2020-11-12 22:20:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async syncUserGroups(userName, info) {
|
|
|
|
let {client} = this;
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
let opts = {
|
2020-11-12 22:20:25 +00:00
|
|
|
scope: 'sub',
|
|
|
|
attributes: ['dn'],
|
|
|
|
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
2020-11-13 12:20:37 +00:00
|
|
|
};
|
|
|
|
let oldGroups = await client.searchAll(this.groupDn, opts);
|
2020-11-12 22:20:25 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
let opts = {
|
2020-11-12 22:20:25 +00:00
|
|
|
scope: 'sub',
|
|
|
|
attributes: ['uid'],
|
|
|
|
filter: `uid=*`
|
2020-11-13 12:20:37 +00:00
|
|
|
};
|
|
|
|
await client.searchForeach(this.userDn, opts,
|
|
|
|
o => usersToSync.add(o.uid));
|
2020-11-12 22:20:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
async syncRoles() {
|
|
|
|
let $ = app.models;
|
|
|
|
let {
|
|
|
|
client,
|
|
|
|
accountConfig
|
|
|
|
} = this;
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
// Prepare data
|
2020-11-12 22:20:25 +00:00
|
|
|
|
|
|
|
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};
|
|
|
|
});
|
|
|
|
|
2020-11-13 12:20:37 +00:00
|
|
|
// 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
|
|
|
|
|
2020-11-12 22:20:25 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
2020-11-13 12:20:37 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|