Merge branch 'test' into dev
This commit is contained in:
commit
dde89e3b9f
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -35,109 +35,104 @@ module.exports = Self => {
|
||||||
accountConfig
|
accountConfig
|
||||||
} = this;
|
} = this;
|
||||||
|
|
||||||
let {user} = info;
|
let newEntry;
|
||||||
|
|
||||||
let res = await client.search(this.userDn, {
|
if (info.hasAccount) {
|
||||||
scope: 'sub',
|
let {user} = info;
|
||||||
attributes: ['userPassword', 'sambaNTPassword'],
|
|
||||||
filter: `&(uid=${userName})`
|
|
||||||
});
|
|
||||||
|
|
||||||
let oldUser;
|
let oldUser = await client.searchOne(this.userDn, {
|
||||||
await new Promise((resolve, reject) => {
|
scope: 'sub',
|
||||||
res.on('error', reject);
|
attributes: ['userPassword', 'sambaNTPassword'],
|
||||||
res.on('searchEntry', e => oldUser = e.object);
|
filter: `&(uid=${userName})`
|
||||||
res.on('end', resolve);
|
});
|
||||||
});
|
|
||||||
|
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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let prop in newEntry) {
|
||||||
|
if (newEntry[prop] == null)
|
||||||
|
delete newEntry[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove and recreate (if applicable) user
|
||||||
|
|
||||||
|
let dn = `uid=${userName},${this.userDn}`;
|
||||||
|
let operation;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let dn = `uid=${userName},${this.userDn}`;
|
|
||||||
await client.del(dn);
|
await client.del(dn);
|
||||||
|
operation = 'delete';
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!info.hasAccount) {
|
if (info.hasAccount) {
|
||||||
if (oldUser)
|
await client.add(dn, newEntry);
|
||||||
console.log(` -> '${userName}' removed from LDAP`);
|
operation = 'add';
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let nickname = user.nickname || userName;
|
if (operation === 'delete')
|
||||||
let nameArgs = nickname.trim().split(' ');
|
console.log(` -> User '${userName}' removed from LDAP`);
|
||||||
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) {
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -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(` -> User '${userName}' disabled on Samba`);
|
||||||
console.log(` -> '${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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue