376 lines
11 KiB
JavaScript
376 lines
11 KiB
JavaScript
const ldap = require('../../util/ldapjs-extra');
|
|
const nthash = require('smbhash').nthash;
|
|
const ssh = require('node-ssh');
|
|
const crypto = require('crypto');
|
|
|
|
module.exports = Self => {
|
|
Self.remoteMethod('sync', {
|
|
description: 'Synchronizes the user with the other user databases',
|
|
accepts: [
|
|
{
|
|
arg: 'userName',
|
|
type: 'string',
|
|
description: 'The user name',
|
|
required: true
|
|
}, {
|
|
arg: 'password',
|
|
type: 'string',
|
|
description: 'The password'
|
|
}
|
|
],
|
|
http: {
|
|
path: `/:userName/sync`,
|
|
verb: 'PATCH'
|
|
}
|
|
});
|
|
|
|
Self.sync = async function(userName, password) {
|
|
let $ = Self.app.models;
|
|
|
|
let user = await $.Account.findOne({
|
|
fields: ['id'],
|
|
where: {name: userName}
|
|
});
|
|
let isSync = !await $.UserSync.exists(userName);
|
|
|
|
if (user && isSync) return;
|
|
|
|
let sync = await Self.syncInit();
|
|
try {
|
|
await Self.doSync(sync, userName, password);
|
|
} finally {
|
|
await Self.syncDeinit(sync);
|
|
}
|
|
};
|
|
|
|
Self.syncInit = async function() {
|
|
let $ = Self.app.models;
|
|
|
|
let accountConfig = await $.AccountConfig.findOne({
|
|
fields: ['homedir', 'shell', 'idBase']
|
|
});
|
|
let mailConfig = await $.MailConfig.findOne({
|
|
fields: ['domain']
|
|
});
|
|
|
|
// LDAP
|
|
|
|
let ldapClient;
|
|
let ldapConfig = await $.LdapConfig.findOne({
|
|
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
|
});
|
|
|
|
if (ldapConfig) {
|
|
ldapClient = ldap.createClient({
|
|
url: `ldap://${ldapConfig.host}:389`
|
|
});
|
|
|
|
let ldapPassword = Buffer
|
|
.from(ldapConfig.password, 'base64')
|
|
.toString('ascii');
|
|
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
|
|
}
|
|
|
|
// Samba
|
|
|
|
let sambaClient;
|
|
let sambaConfig = await $.SambaConfig.findOne({
|
|
fields: ['host', 'sshUser', 'sshPass']
|
|
});
|
|
|
|
if (sambaConfig) {
|
|
let sshPassword = Buffer
|
|
.from(sambaConfig.sshPass, 'base64')
|
|
.toString('ascii');
|
|
|
|
sambaClient = new ssh.NodeSSH();
|
|
await sambaClient.connect({
|
|
host: sambaConfig.host,
|
|
username: sambaConfig.sshUser,
|
|
password: sshPassword
|
|
});
|
|
}
|
|
|
|
return {
|
|
accountConfig,
|
|
mailConfig,
|
|
ldapConfig,
|
|
ldapClient,
|
|
sambaClient
|
|
};
|
|
};
|
|
|
|
Self.syncDeinit = async function(sync) {
|
|
// FIXME: Cannot disconnect, hangs on undind() call
|
|
// if (sync.ldapClient)
|
|
// await sync.ldapClient.unbind();
|
|
|
|
if (sync.sambaClient)
|
|
await sync.sambaClient.dispose();
|
|
};
|
|
|
|
Self.doSync = async function(sync, userName, password) {
|
|
let $ = Self.app.models;
|
|
let {
|
|
accountConfig,
|
|
mailConfig,
|
|
ldapConfig,
|
|
ldapClient,
|
|
sambaClient
|
|
} = sync;
|
|
|
|
// Skip conflicting users
|
|
if (!userName || ['administrator', 'root'].indexOf(userName.toLowerCase()) >= 0)
|
|
return;
|
|
|
|
let user = await $.Account.findOne({
|
|
where: {name: userName},
|
|
fields: [
|
|
'id',
|
|
'nickname',
|
|
'email',
|
|
'lang',
|
|
'roleFk',
|
|
'sync',
|
|
'active',
|
|
'created',
|
|
'bcryptPassword',
|
|
'updated'
|
|
],
|
|
include: {
|
|
relation: 'roles',
|
|
scope: {
|
|
include: {
|
|
relation: 'inherits',
|
|
scope: {
|
|
fields: ['name']
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let extraParams;
|
|
let hasAccount = false;
|
|
|
|
if (user) {
|
|
extraParams = {
|
|
corporateMail: `${userName}@${mailConfig.domain}`,
|
|
uidNumber: accountConfig.idBase + user.id
|
|
};
|
|
|
|
hasAccount = user.active
|
|
&& await $.UserAccount.exists(user.id);
|
|
}
|
|
|
|
// Database
|
|
|
|
if (user && user.active) {
|
|
let bcryptPassword = password
|
|
? $.User.hashPassword(password)
|
|
: user.bcryptPassword;
|
|
|
|
await $.Account.upsertWithWhere({id: user.id},
|
|
{bcryptPassword}
|
|
);
|
|
|
|
let appUser = {
|
|
id: user.id,
|
|
username: userName,
|
|
email: user.email,
|
|
created: user.created,
|
|
updated: user.updated
|
|
};
|
|
if (bcryptPassword)
|
|
appUser.password = bcryptPassword;
|
|
|
|
if (await $.user.exists(user.id))
|
|
await $.user.replaceById(user.id, appUser);
|
|
else if (bcryptPassword)
|
|
await $.user.upsert(appUser);
|
|
} else
|
|
await $.user.destroyAll({username: userName});
|
|
|
|
// SIP
|
|
|
|
if (hasAccount && password) {
|
|
await Self.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
|
[user.id, password]
|
|
);
|
|
}
|
|
|
|
// LDAP
|
|
|
|
if (ldapClient) {
|
|
// Deletes user
|
|
|
|
res = await ldapClient.search(ldapConfig.baseDn, {
|
|
scope: 'sub',
|
|
attributes: ['userPassword', 'sambaNTPassword'],
|
|
filter: `&(uid=${userName})(objectClass=inetOrgPerson)`
|
|
});
|
|
|
|
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.baseDn}`;
|
|
await ldapClient.del(dn);
|
|
} catch (e) {
|
|
if (e.name !== 'NoSuchObjectError') throw e;
|
|
}
|
|
|
|
// Removes user from groups
|
|
|
|
res = await ldapClient.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 (oldGroup of oldGroups) {
|
|
let change = new ldap.Change({
|
|
operation: 'delete',
|
|
modification: {memberUid: userName}
|
|
});
|
|
reqs.push(ldapClient.modify(oldGroup.dn, change));
|
|
}
|
|
await Promise.all(reqs);
|
|
|
|
if (hasAccount) {
|
|
// Recreates user
|
|
|
|
let nickname = user.nickname || userName;
|
|
let nameArgs = nickname.split(' ');
|
|
let sn = nameArgs.length > 1
|
|
? nameArgs.splice(1).join(' ')
|
|
: '-';
|
|
|
|
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
|
let newEntry = {
|
|
uid: userName,
|
|
objectClass: [
|
|
'inetOrgPerson',
|
|
'posixAccount',
|
|
'sambaSamAccount'
|
|
],
|
|
cn: nickname,
|
|
displayName: nickname,
|
|
givenName: nameArgs[0],
|
|
sn,
|
|
mail: extraParams.corporateMail,
|
|
preferredLanguage: user.lang,
|
|
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
|
loginShell: accountConfig.shell,
|
|
uidNumber: extraParams.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 ldapClient.add(dn, newEntry);
|
|
|
|
// Adds user to groups
|
|
|
|
let reqs = [];
|
|
for (let role of 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(ldapClient.modify(dn, change));
|
|
}
|
|
await Promise.all(reqs);
|
|
}
|
|
}
|
|
|
|
// Samba
|
|
|
|
if (sambaClient) {
|
|
if (hasAccount) {
|
|
try {
|
|
await sambaClient.exec('samba-tool user create', [
|
|
userName,
|
|
'--uid-number', `${extraParams.uidNumber}`,
|
|
'--mail-address', extraParams.corporateMail,
|
|
'--random-password'
|
|
]);
|
|
} catch (e) {}
|
|
|
|
await sambaClient.exec('samba-tool user setexpiry', [
|
|
userName,
|
|
'--noexpiry'
|
|
]);
|
|
|
|
if (password) {
|
|
await sambaClient.exec('samba-tool user setpassword', [
|
|
userName,
|
|
'--newpassword', password
|
|
]);
|
|
await sambaClient.exec('samba-tool user enable', [
|
|
userName
|
|
]);
|
|
}
|
|
|
|
await sambaClient.exec('mkhomedir_helper', [
|
|
userName,
|
|
'0027'
|
|
]);
|
|
} else {
|
|
try {
|
|
await sambaClient.exec('samba-tool user disable', [
|
|
userName
|
|
]);
|
|
} catch (e) {}
|
|
}
|
|
}
|
|
|
|
// Mark as synchronized
|
|
|
|
await $.UserSync.destroyById(userName);
|
|
};
|
|
};
|