266 lines
8.3 KiB
JavaScript
266 lines
8.3 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 {user} = info;
|
||
|
|
||
|
let res = await client.search(this.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},${this.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},${this.userDn}`;
|
||
|
let 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];
|
||
|
}
|
||
|
|
||
|
await client.add(dn, newEntry);
|
||
|
},
|
||
|
|
||
|
async syncUserGroups(userName, info) {
|
||
|
let {client} = this;
|
||
|
|
||
|
let res = await client.search(this.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},${this.groupDn}`;
|
||
|
reqs.push(client.modify(dn, change));
|
||
|
}
|
||
|
await Promise.all(reqs);
|
||
|
},
|
||
|
|
||
|
async getUsers(usersToSync) {
|
||
|
let {client} = this;
|
||
|
|
||
|
let res = await client.search(this.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 $ = app.models;
|
||
|
let {
|
||
|
client,
|
||
|
accountConfig
|
||
|
} = this;
|
||
|
|
||
|
// Delete roles
|
||
|
|
||
|
let opts = {
|
||
|
scope: 'sub',
|
||
|
attributes: ['dn'],
|
||
|
filter: 'objectClass=posixGroup'
|
||
|
};
|
||
|
let res = await client.search(this.groupDn, opts);
|
||
|
|
||
|
let reqs = [];
|
||
|
await new Promise((resolve, reject) => {
|
||
|
res.on('error', err => {
|
||
|
if (err.name === 'NoSuchObjectError')
|
||
|
err = new Error(`Object '${this.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},${this.groupDn}`;
|
||
|
reqs.push(client.add(dn, newEntry));
|
||
|
}
|
||
|
await Promise.all(reqs);
|
||
|
}
|
||
|
});
|
||
|
};
|