salix/modules/account/back/models/samba-config.js

175 lines
5.8 KiB
JavaScript
Raw Permalink Normal View History

const ldap = require('../util/ldapjs-extra');
2023-11-16 22:07:26 +00:00
const execFile = require('child_process').execFile;
2024-05-21 13:11:32 +00:00
const isProduction = require('vn-loopback/server/boot/isProduction');
2020-11-13 12:20:37 +00:00
/**
* Summary of userAccountControl flags:
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
*/
const UserAccountControlFlags = {
ACCOUNTDISABLE: 0x2,
DONT_EXPIRE_PASSWD: 0x10000
2020-11-13 12:20:37 +00:00
};
module.exports = Self => {
2024-05-21 11:50:45 +00:00
const shouldSync = isProduction();
2023-11-16 22:07:26 +00:00
Self.getLinker = async function() {
return await Self.findOne({
fields: [
'host',
'adDomain',
'adController',
'adUser',
'adPassword',
2023-11-16 22:07:26 +00:00
'userDn',
'verifyCert'
]
});
};
Object.assign(Self.prototype, {
async init() {
2023-11-16 22:07:26 +00:00
const baseDn = this.adDomain
.split('.')
.map(part => `dc=${part}`)
.join(',');
2023-11-17 07:56:25 +00:00
const bindDn = `cn=${this.adUser},cn=Users,${baseDn}`;
2023-11-16 22:07:26 +00:00
const adClient = ldap.createClient({
url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert}
});
adClient.on('error', () => {});
2023-11-17 07:56:25 +00:00
await adClient.bind(bindDn, this.adPassword);
Object.assign(this, {
2023-11-16 22:07:26 +00:00
adClient,
2023-11-17 07:56:25 +00:00
fullUsersDn: `${this.userDn},${baseDn}`,
bindDn
});
},
async deinit() {
await this.adClient.unbind();
},
2023-11-16 22:07:26 +00:00
async sambaTool(command, args = []) {
2023-11-17 07:56:25 +00:00
let authArgs = [
'--URL', `ldaps://${this.adController}`,
'--simple-bind-dn', this.bindDn,
2023-11-16 22:07:26 +00:00
'--password', this.adPassword
];
2023-11-17 07:56:25 +00:00
if (!this.verifyCert)
authArgs.push('--option', 'tls verify peer = no_check');
2023-11-16 22:07:26 +00:00
const allArgs = [command].concat(
args, authArgs
);
if (!shouldSync) return;
return await new Promise((resolve, reject) => {
execFile('samba-tool', allArgs, (err, stdout, stderr) => {
if (err)
reject(err);
else
resolve({stdout, stderr});
});
});
},
2023-11-16 22:07:26 +00:00
async getAdUser(userName) {
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
2020-11-13 12:20:37 +00:00
scope: 'sub',
2023-11-16 22:07:26 +00:00
attributes: [
'dn',
'userAccountControl',
'uidNumber',
'accountExpires',
'mail'
],
2020-11-13 12:20:37 +00:00
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
});
2023-11-16 22:07:26 +00:00
if (sambaUser) {
for (const intProp of ['uidNumber', 'userAccountControl']) {
if (sambaUser[intProp] != null)
sambaUser[intProp] = parseInt(sambaUser[intProp]);
}
}
return sambaUser;
},
async syncUser(userName, info, password) {
let sambaUser = await this.getAdUser(userName);
let entry;
2020-11-13 12:20:37 +00:00
if (info.hasAccount) {
2020-11-13 12:20:37 +00:00
if (!sambaUser) {
2023-11-16 22:07:26 +00:00
await this.sambaTool('user', [
'create', userName,
'--userou', this.userDn,
'--random-password'
]);
2023-11-16 22:07:26 +00:00
sambaUser = await this.getAdUser(userName);
2020-11-13 12:20:37 +00:00
}
if (password) {
2023-11-16 22:07:26 +00:00
await this.sambaTool('user', [
'setpassword', userName,
'--newpassword', password
]);
}
2023-11-16 22:07:26 +00:00
entry = {
userAccountControl: (sambaUser.userAccountControl
| UserAccountControlFlags.DONT_EXPIRE_PASSWD)
2023-11-16 22:07:26 +00:00
& ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
mail: info.corporateMail
};
} else if (sambaUser) {
entry = {
userAccountControl: sambaUser.userAccountControl
| UserAccountControlFlags.ACCOUNTDISABLE
};
// eslint-disable-next-line no-console
2020-11-13 12:20:37 +00:00
console.log(` -> User '${userName}' disabled on Samba`);
}
2023-11-16 22:07:26 +00:00
if (sambaUser && entry) {
const changes = [];
for (const prop in entry) {
if (sambaUser[prop] == entry[prop]) continue;
changes.push(new ldap.Change({
operation: 'replace',
modification: {
[prop]: entry[prop]
}
}));
}
2023-11-16 22:10:30 +00:00
if (changes.length && shouldSync)
2023-11-16 22:07:26 +00:00
await this.adClient.modify(sambaUser.dn, changes);
}
},
/**
* Gets Samba enabled users.
*
* @param {Set} usersToSync
*/
async getUsers(usersToSync) {
2020-11-13 12:20:37 +00:00
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
2023-11-17 07:56:25 +00:00
const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}`
+ `:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
2023-11-16 22:07:26 +00:00
const opts = {
scope: 'sub',
attributes: ['sAMAccountName'],
2020-11-13 12:20:37 +00:00
filter: `(&(objectClass=user)(${filter}))`
};
2023-11-16 22:07:26 +00:00
await this.adClient.searchForeach(this.fullUsersDn, opts,
2020-11-13 12:20:37 +00:00
o => usersToSync.add(o.sAMAccountName));
}
});
};