User sync fixes
This commit is contained in:
parent
937d5beefe
commit
9b4f8697b9
|
@ -3,3 +3,5 @@ ALTER TABLE `account`.`roleRole`
|
|||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
UPDATE `account`.`role` SET id = 100 WHERE `name` = 'root';
|
||||
|
||||
CALL account.role_sync;
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = Self => {
|
|||
attributes: ['dn'],
|
||||
filter: 'objectClass=posixGroup'
|
||||
};
|
||||
res = await client.search(ldapConfig.groupDn, opts);
|
||||
let res = await client.search(ldapConfig.groupDn, opts);
|
||||
|
||||
let reqs = [];
|
||||
await new Promise((resolve, reject) => {
|
||||
|
@ -110,8 +110,7 @@ module.exports = Self => {
|
|||
err = e;
|
||||
}
|
||||
|
||||
// FIXME: Cannot disconnect, hangs on undind() call
|
||||
// await client.unbind();
|
||||
await client.unbind();
|
||||
if (err) throw err;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,107 +1,37 @@
|
|||
const ldap = require('../../util/ldapjs-extra');
|
||||
const ssh = require('node-ssh');
|
||||
|
||||
const SyncEngine = require('../../util/sync-engine');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('syncAll', {
|
||||
description: 'Synchronizes user database with LDAP and Samba',
|
||||
http: {
|
||||
path: `/sync-all`,
|
||||
path: `/syncAll`,
|
||||
verb: 'PATCH'
|
||||
}
|
||||
});
|
||||
|
||||
Self.syncAll = async function() {
|
||||
let $ = Self.app.models;
|
||||
let usersToSync = new Set();
|
||||
|
||||
// Database
|
||||
let se = new SyncEngine();
|
||||
await se.init($);
|
||||
|
||||
let accounts = await $.UserAccount.find({
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
where: {active: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
let usersToSync = await se.getUsers();
|
||||
usersToSync = Array.from(usersToSync.values())
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
for (let account of accounts) {
|
||||
let user = account.user();
|
||||
if (!user) continue;
|
||||
usersToSync.add(user.name);
|
||||
}
|
||||
|
||||
// LDAP
|
||||
|
||||
let ldapConfig = await $.LdapConfig.findOne({
|
||||
fields: ['host', 'rdn', 'password', 'baseDn']
|
||||
});
|
||||
|
||||
if (ldapConfig) {
|
||||
let ldapClient = ldap.createClient({
|
||||
url: `ldap://${ldapConfig.host}:389`
|
||||
});
|
||||
|
||||
let ldapPassword = Buffer
|
||||
.from(ldapConfig.password, 'base64')
|
||||
.toString('ascii');
|
||||
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
|
||||
|
||||
res = await ldapClient.search(ldapConfig.baseDn, {
|
||||
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);
|
||||
});
|
||||
|
||||
// FIXME: Cannot disconnect, hangs on undind() call
|
||||
// await ldapClient.unbind();
|
||||
}
|
||||
|
||||
// Samba
|
||||
|
||||
let sambaConfig = await $.SambaConfig.findOne({
|
||||
fields: ['host', 'sshUser', 'sshPass']
|
||||
});
|
||||
|
||||
if (sambaConfig) {
|
||||
let sshPassword = Buffer
|
||||
.from(sambaConfig.sshPass, 'base64')
|
||||
.toString('ascii');
|
||||
|
||||
let sshClient = new ssh.NodeSSH();
|
||||
await sshClient.connect({
|
||||
host: sambaConfig.host,
|
||||
username: sambaConfig.sshUser,
|
||||
password: sshPassword
|
||||
});
|
||||
|
||||
let res = await sshClient.execCommand('samba-tool user list');
|
||||
let users = res.stdout.split('\n');
|
||||
for (let user of users) usersToSync.add(user.trim());
|
||||
|
||||
await sshClient.dispose();
|
||||
}
|
||||
|
||||
// Syncing
|
||||
|
||||
$.RoleInherit.sync();
|
||||
|
||||
let sync = await Self.syncInit();
|
||||
for (let user of usersToSync.values()) {
|
||||
for (let user of usersToSync) {
|
||||
try {
|
||||
await Self.doSync(sync, user);
|
||||
console.log(`Synchronizing user '${user}'`);
|
||||
await se.sync(user);
|
||||
console.log(` -> '${user}' sinchronized`);
|
||||
} catch (err) {
|
||||
console.error(`Cannot sync user '${user}':`, err.message);
|
||||
console.error(` -> '${user}' synchronization error:`, err.message);
|
||||
}
|
||||
}
|
||||
await Self.syncDeinit(sync);
|
||||
|
||||
await se.deinit();
|
||||
|
||||
await $.RoleInherit.sync();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
const ldap = require('../../util/ldapjs-extra');
|
||||
const nthash = require('smbhash').nthash;
|
||||
const ssh = require('node-ssh');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const SyncEngine = require('../../util/sync-engine');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('sync', {
|
||||
|
@ -35,341 +33,19 @@ module.exports = Self => {
|
|||
|
||||
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);
|
||||
});
|
||||
let err;
|
||||
let se = new SyncEngine();
|
||||
await se.init($);
|
||||
|
||||
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 se.sync(userName, password, true);
|
||||
await $.UserSync.destroyById(userName);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
await se.deinit();
|
||||
if (err) throw err;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Base class for user synchronizators.
|
||||
*
|
||||
* @property {Array<Model>} $
|
||||
* @property {Object} accountConfig
|
||||
* @property {Object} mailConfig
|
||||
*/
|
||||
class SyncConnector {
|
||||
/**
|
||||
* Initalizes the connector.
|
||||
*/
|
||||
async init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users to synchronize.
|
||||
*
|
||||
* @param {Set} usersToSync Set where users are added
|
||||
*/
|
||||
async getUsers(usersToSync) {}
|
||||
|
||||
/**
|
||||
* Synchronizes a user.
|
||||
*
|
||||
* @param {Object} info User information
|
||||
* @param {String} userName The user name
|
||||
* @param {String} password Thepassword
|
||||
*/
|
||||
async sync(info, userName, password) {}
|
||||
|
||||
/**
|
||||
* Synchronizes user groups.
|
||||
*
|
||||
* @param {User} user Instace of user
|
||||
* @param {String} userName The user name
|
||||
*/
|
||||
async syncGroups(user, userName) {}
|
||||
|
||||
/**
|
||||
* Deinitalizes the connector.
|
||||
*/
|
||||
async deinit() {}
|
||||
}
|
||||
|
||||
SyncConnector.connectors = [];
|
||||
module.exports = SyncConnector;
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
|
||||
class SyncDb extends SyncConnector {
|
||||
async sync(info, userName, password) {
|
||||
let {$} = this;
|
||||
let {user} = info;
|
||||
|
||||
if (user && user.active) {
|
||||
let bcryptPassword = password
|
||||
? $.User.hashPassword(password)
|
||||
: user.bcryptPassword;
|
||||
|
||||
await $.Account.upsertWithWhere({id: user.id},
|
||||
{bcryptPassword}
|
||||
);
|
||||
|
||||
let dbUser = {
|
||||
id: user.id,
|
||||
username: userName,
|
||||
email: user.email,
|
||||
created: user.created,
|
||||
updated: user.updated
|
||||
};
|
||||
if (bcryptPassword)
|
||||
dbUser.password = bcryptPassword;
|
||||
|
||||
if (await $.user.exists(user.id))
|
||||
await $.user.replaceById(user.id, dbUser);
|
||||
else
|
||||
await $.user.create(dbUser);
|
||||
} else
|
||||
await $.user.destroyAll({username: userName});
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let accounts = await this.$.UserAccount.find({
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
where: {active: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (let account of accounts) {
|
||||
let user = account.user();
|
||||
if (!user) continue;
|
||||
usersToSync.add(user.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncDb);
|
||||
module.exports = SyncDb;
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
require('./sync-db');
|
||||
require('./sync-sip');
|
||||
require('./sync-ldap');
|
||||
require('./sync-samba');
|
||||
|
||||
module.exports = class SyncEngine {
|
||||
async init($) {
|
||||
let accountConfig = await $.AccountConfig.findOne({
|
||||
fields: ['homedir', 'shell', 'idBase']
|
||||
});
|
||||
let mailConfig = await $.MailConfig.findOne({
|
||||
fields: ['domain']
|
||||
});
|
||||
|
||||
let connectors = [];
|
||||
|
||||
for (let ConnectorClass of SyncConnector.connectors) {
|
||||
let connector = new ConnectorClass();
|
||||
Object.assign(connector, {
|
||||
se: this,
|
||||
$
|
||||
});
|
||||
if (!await connector.init()) continue;
|
||||
connectors.push(connector);
|
||||
}
|
||||
|
||||
Object.assign(this, {
|
||||
connectors,
|
||||
$,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
});
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
for (let connector of this.connectors)
|
||||
await connector.deinit();
|
||||
}
|
||||
|
||||
async sync(userName, password, syncGroups) {
|
||||
let {
|
||||
$,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
} = this;
|
||||
|
||||
if (!userName) return;
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
// Skip conflicting users
|
||||
if (['administrator', 'root'].indexOf(userName) >= 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) {
|
||||
hasAccount = user.active
|
||||
&& await $.UserAccount.exists(user.id);
|
||||
|
||||
extraParams = {
|
||||
corporateMail: `${userName}@${mailConfig.domain}`,
|
||||
uidNumber: accountConfig.idBase + user.id
|
||||
};
|
||||
}
|
||||
|
||||
let info = {
|
||||
user,
|
||||
extraParams,
|
||||
hasAccount,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
};
|
||||
|
||||
let errs = [];
|
||||
|
||||
for (let connector of this.connectors) {
|
||||
try {
|
||||
await connector.sync(info, userName, password);
|
||||
if (syncGroups)
|
||||
await connector.syncGroups(user, userName);
|
||||
} catch (err) {
|
||||
errs.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (errs.length) throw errs[0];
|
||||
}
|
||||
|
||||
async getUsers() {
|
||||
let usersToSync = new Set();
|
||||
|
||||
for (let connector of this.connectors)
|
||||
await connector.getUsers(usersToSync);
|
||||
|
||||
return usersToSync;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,203 @@
|
|||
|
||||
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: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
||||
});
|
||||
if (!ldapConfig) return false;
|
||||
|
||||
let client = ldap.createClient({
|
||||
url: `ldap://${ldapConfig.host}:389`
|
||||
});
|
||||
|
||||
let ldapPassword = Buffer
|
||||
.from(ldapConfig.password, 'base64')
|
||||
.toString('ascii');
|
||||
await client.bind(ldapConfig.rdn, ldapPassword);
|
||||
|
||||
Object.assign(this, {
|
||||
ldapConfig,
|
||||
client
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
if (this.client)
|
||||
await this.client.unbind();
|
||||
}
|
||||
|
||||
async sync(info, userName, password) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client,
|
||||
} = this;
|
||||
|
||||
let {
|
||||
user,
|
||||
hasAccount,
|
||||
extraParams,
|
||||
accountConfig
|
||||
} = info;
|
||||
|
||||
let res = await client.search(ldapConfig.baseDn, {
|
||||
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.baseDn}`;
|
||||
await client.del(dn);
|
||||
} catch (e) {
|
||||
if (e.name !== 'NoSuchObjectError') throw e;
|
||||
}
|
||||
|
||||
if (!hasAccount) {
|
||||
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.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 client.add(dn, newEntry);
|
||||
}
|
||||
|
||||
async syncGroups(info, userName) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client
|
||||
} = this;
|
||||
|
||||
let {
|
||||
user,
|
||||
hasAccount
|
||||
} = info;
|
||||
|
||||
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 (hasAccount) {
|
||||
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(client.modify(dn, change));
|
||||
}
|
||||
await Promise.all(reqs);
|
||||
}
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client
|
||||
} = this;
|
||||
|
||||
let res = await client.search(ldapConfig.baseDn, {
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncLdap);
|
||||
module.exports = SyncLdap;
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
const ssh = require('node-ssh');
|
||||
|
||||
class SyncSamba extends SyncConnector {
|
||||
async init() {
|
||||
let sambaConfig = await this.$.SambaConfig.findOne({
|
||||
fields: ['host', 'sshUser', 'sshPass']
|
||||
});
|
||||
if (!sambaConfig) return false;
|
||||
|
||||
let sshPassword = Buffer
|
||||
.from(sambaConfig.sshPass, 'base64')
|
||||
.toString('ascii');
|
||||
|
||||
let client = new ssh.NodeSSH();
|
||||
await client.connect({
|
||||
host: sambaConfig.host,
|
||||
username: sambaConfig.sshUser,
|
||||
password: sshPassword
|
||||
});
|
||||
|
||||
Object.assign(this, {
|
||||
sambaConfig,
|
||||
client
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
if (this.client)
|
||||
await this.client.dispose();
|
||||
}
|
||||
|
||||
async sync(info, userName, password) {
|
||||
let {
|
||||
client
|
||||
} = this;
|
||||
|
||||
let {
|
||||
hasAccount,
|
||||
extraParams
|
||||
} = info;
|
||||
|
||||
if (hasAccount) {
|
||||
try {
|
||||
await client.exec('samba-tool user create', [
|
||||
userName,
|
||||
'--uid-number', `${extraParams.uidNumber}`,
|
||||
'--mail-address', extraParams.corporateMail,
|
||||
'--random-password'
|
||||
]);
|
||||
} catch (e) {}
|
||||
|
||||
await client.exec('samba-tool user setexpiry', [
|
||||
userName,
|
||||
'--noexpiry'
|
||||
]);
|
||||
|
||||
if (password) {
|
||||
await client.exec('samba-tool user setpassword', [
|
||||
userName,
|
||||
'--newpassword', password
|
||||
]);
|
||||
await client.exec('samba-tool user enable', [
|
||||
userName
|
||||
]);
|
||||
}
|
||||
|
||||
await client.exec('mkhomedir_helper', [
|
||||
userName,
|
||||
'0027'
|
||||
]);
|
||||
} else {
|
||||
try {
|
||||
await client.exec('samba-tool user disable', [
|
||||
userName
|
||||
]);
|
||||
console.log(` -> '${userName}' disabled on Samba`);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let {client} = this;
|
||||
let res = await client.execCommand('samba-tool user list');
|
||||
let users = res.stdout.split('\n');
|
||||
for (let user of users) usersToSync.add(user.trim());
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncSamba);
|
||||
module.exports = SyncSamba;
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
|
||||
class SyncSip extends SyncConnector {
|
||||
async sync(info, userName, password) {
|
||||
if (!info.hasAccount || !password) return;
|
||||
|
||||
await this.$.Account.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
||||
[info.user.id, password]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncSip);
|
||||
module.exports = SyncSip;
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,7 @@
|
|||
"helmet": "^3.21.2",
|
||||
"i18n": "^0.8.4",
|
||||
"imap": "^0.8.19",
|
||||
"ldapjs": "^1.0.2",
|
||||
"ldapjs": "^2.2.0",
|
||||
"loopback": "^3.26.0",
|
||||
"loopback-boot": "^2.27.1",
|
||||
"loopback-component-explorer": "^6.5.0",
|
||||
|
|
Loading…
Reference in New Issue