Merge branch 'test' into dev

This commit is contained in:
Juan Ferrer 2020-11-13 13:50:15 +01:00
commit dde89e3b9f
4 changed files with 178 additions and 153 deletions

View File

@ -20,7 +20,7 @@ module.exports = Self => {
async syncUsers() { async syncUsers() {
let instance = await Self.getInstance(); let instance = await Self.getInstance();
let usersToSync = instance.getUsers(); let usersToSync = await instance.synchronizerGetUsers();
usersToSync = Array.from(usersToSync.values()) usersToSync = Array.from(usersToSync.values())
.sort((a, b) => a.localeCompare(b)); .sort((a, b) => a.localeCompare(b));
@ -28,9 +28,9 @@ module.exports = Self => {
try { try {
console.log(`Synchronizing user '${userName}'`); console.log(`Synchronizing user '${userName}'`);
await instance.synchronizerSyncUser(userName); await instance.synchronizerSyncUser(userName);
console.log(` -> '${userName}' sinchronized`); console.log(` -> User '${userName}' sinchronized`);
} catch (err) { } catch (err) {
console.error(` -> '${userName}' synchronization error:`, err.message); console.error(` -> User '${userName}' synchronization error:`, err.message);
} }
} }
@ -171,7 +171,7 @@ module.exports = Self => {
}, },
async syncUser(userName, info, password) { async syncUser(userName, info, password) {
if (info.user) if (info.user && password)
await app.models.user.setPassword(info.user.id, password); await app.models.user.setPassword(info.user.id, password);
}, },

View File

@ -35,42 +35,24 @@ module.exports = Self => {
accountConfig accountConfig
} = this; } = this;
let newEntry;
if (info.hasAccount) {
let {user} = info; let {user} = info;
let res = await client.search(this.userDn, { let oldUser = await client.searchOne(this.userDn, {
scope: 'sub', scope: 'sub',
attributes: ['userPassword', 'sambaNTPassword'], attributes: ['userPassword', 'sambaNTPassword'],
filter: `&(uid=${userName})` 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 nickname = user.nickname || userName;
let nameArgs = nickname.trim().split(' '); let nameArgs = nickname.trim().split(' ');
let sn = nameArgs.length > 1 let sn = nameArgs.length > 1
? nameArgs.splice(1).join(' ') ? nameArgs.splice(1).join(' ')
: '-'; : '-';
let dn = `uid=${userName},${this.userDn}`; newEntry = {
let newEntry = {
uid: userName, uid: userName,
objectClass: [ objectClass: [
'inetOrgPerson', 'inetOrgPerson',
@ -119,25 +101,38 @@ module.exports = Self => {
if (newEntry[prop] == null) if (newEntry[prop] == null)
delete newEntry[prop]; 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); await client.add(dn, newEntry);
operation = 'add';
}
if (operation === 'delete')
console.log(` -> User '${userName}' removed from LDAP`);
}, },
async syncUserGroups(userName, info) { async syncUserGroups(userName, info) {
let {client} = this; let {client} = this;
let res = await client.search(this.groupDn, { let opts = {
scope: 'sub', scope: 'sub',
attributes: ['dn'], attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)` filter: `&(memberUid=${userName})(objectClass=posixGroup)`
}); };
let oldGroups = await client.searchAll(this.groupDn, opts);
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 = []; let reqs = [];
for (let oldGroup of oldGroups) { for (let oldGroup of oldGroups) {
@ -167,17 +162,13 @@ module.exports = Self => {
async getUsers(usersToSync) { async getUsers(usersToSync) {
let {client} = this; let {client} = this;
let res = await client.search(this.userDn, { let opts = {
scope: 'sub', scope: 'sub',
attributes: ['uid'], attributes: ['uid'],
filter: `uid=*` filter: `uid=*`
}); };
await client.searchForeach(this.userDn, opts,
await new Promise((resolve, reject) => { o => usersToSync.add(o.uid));
res.on('error', reject);
res.on('searchEntry', e => usersToSync.add(e.object.uid));
res.on('end', resolve);
});
}, },
async syncRoles() { async syncRoles() {
@ -187,30 +178,7 @@ module.exports = Self => {
accountConfig accountConfig
} = this; } = this;
// Delete roles // Prepare data
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({ let roles = await $.Role.find({
fields: ['id', 'name', 'description'] fields: ['id', 'name', 'description']
@ -238,6 +206,20 @@ module.exports = Self => {
return {key: user.roleFk, val: user.name}; 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 = []; reqs = [];
for (let role of roles) { for (let role of roles) {
let newEntry = { let newEntry = {
@ -263,3 +245,15 @@ module.exports = Self => {
} }
}); });
}; };
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;
}

View File

@ -2,6 +2,14 @@
const ldap = require('../util/ldapjs-extra'); const ldap = require('../util/ldapjs-extra');
const ssh = require('node-ssh'); const ssh = require('node-ssh');
/**
* Summary of userAccountControl flags:
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
*/
const UserAccountControlFlags = {
ACCOUNTDISABLE: 2
};
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { Self.getSynchronizer = async function() {
return await Self.findOne({ return await Self.findOne({
@ -55,8 +63,16 @@ module.exports = Self => {
async syncUser(userName, info, password) { async syncUser(userName, info, password) {
let {sshClient} = this; let {sshClient} = this;
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
scope: 'sub',
attributes: ['userAccountControl'],
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
});
let isEnabled = sambaUser
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
if (info.hasAccount) { if (info.hasAccount) {
try { if (!sambaUser) {
await sshClient.exec('samba-tool user create', [ await sshClient.exec('samba-tool user create', [
userName, userName,
'--uid-number', `${info.uidNumber}`, '--uid-number', `${info.uidNumber}`,
@ -71,58 +87,42 @@ module.exports = Self => {
userName, userName,
'0027' '0027'
]); ]);
} catch (e) {} }
if (!isEnabled) {
await sshClient.exec('samba-tool user enable', [ await sshClient.exec('samba-tool user enable', [
userName userName
]); ]);
}
if (password) { if (password) {
await sshClient.exec('samba-tool user setpassword', [ await sshClient.exec('samba-tool user setpassword', [
userName, userName,
'--newpassword', password '--newpassword', password
]); ]);
} }
} else { } else if (isEnabled) {
try {
await sshClient.exec('samba-tool user disable', [ await sshClient.exec('samba-tool user disable', [
userName userName
]); ]);
console.log(` -> '${userName}' disabled on Samba`); console.log(` -> User '${userName}' disabled on Samba`);
} catch (e) {}
} }
}, },
/** /**
* Gets Samba enabled users. * Gets Samba enabled users.
* *
* Summary of userAccountControl flags:
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
*
* @param {Set} usersToSync * @param {Set} usersToSync
*/ */
async getUsers(usersToSync) { async getUsers(usersToSync) {
let {adClient} = this; const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
let usersDn = this.usersDn(); let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
let opts = { let opts = {
scope: 'sub', scope: 'sub',
attributes: ['sAMAccountName'], attributes: ['sAMAccountName'],
filter: '(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))' filter: `(&(objectClass=user)(${filter}))`
}; };
let res = await adClient.search(usersDn, opts); await this.adClient.searchForeach(this.usersDn(), opts,
o => usersToSync.add(o.sAMAccountName));
await new Promise((resolve, reject) => {
res.on('error', err => {
if (err.name === 'NoSuchObjectError')
err = new Error(`Object '${usersDn}' does not exist`);
reject(err);
});
res.on('searchEntry', e => {
usersToSync.add(e.object.sAMAccountName);
});
res.on('end', resolve);
});
} }
}); });
}; };

View File

@ -26,5 +26,36 @@ function createClient(opts) {
'starttls', 'starttls',
'unbind' 'unbind'
]); ]);
Object.assign(client, {
async searchForeach(base, options, eachFn, controls) {
let res = await this.search(base, options);
await new Promise((resolve, reject) => {
res.on('error', err => {
if (err.name === 'NoSuchObjectError')
err = new Error(`Object '${base}' does not exist`);
reject(err);
});
res.on('searchEntry', e => eachFn(e.object));
res.on('end', resolve);
});
},
async searchAll(base, options, controls) {
let elements = [];
await this.searchForeach(base, options,
o => elements.push(o), controls);
return elements;
},
async searchOne(base, options, controls) {
let object;
await this.searchForeach(base, options,
o => object = o, controls);
return object;
}
});
return client; return client;
} }