diff --git a/db/dump/fixtures.before.sql b/db/dump/fixtures.before.sql
index 30f1ceb5e..ef8bbccc4 100644
--- a/db/dump/fixtures.before.sql
+++ b/db/dump/fixtures.before.sql
@@ -79,8 +79,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre
CALL `account`.`role_sync`;
-INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`)
- SELECT id, LOWER(name), CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2'
+INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`, `sync`)
+ SELECT id, LOWER(name), CONCAT(name, 'Nick'), id, 1, CONCAT(name, '@mydomain.com'), 'en', '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', '$2b$10$UzQHth.9UUQ1T5aiQJ21lOU0oVlbxoqH4PFM9V8T90KNSAcg0eEL2', 1
FROM `account`.`role`
ORDER BY id;
@@ -767,7 +767,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(35, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Somewhere in Philippines', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(36, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Ant-Man Adventure', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(37, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1110, 'Deadpool swords', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL);
-
+
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
(1, 11, 1, 'ready'),
@@ -3836,7 +3836,7 @@ INSERT INTO `vn`.`ledgerConfig` SET
INSERT INTO vn.sectorCollection
SET id = 2,
userFk = 18,
- sectorFk = 1;
+ sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 8,
@@ -3850,9 +3850,13 @@ INSERT INTO vn.saleGroup (userFk, parkingFk, sectorFk, ticketFk)
INSERT INTO vn.sectorCollection
SET id = 3,
userFk = 18,
- sectorFk = 1;
+ sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 9,
sectorCollectionFk = 3,
- saleGroupFk = 6;
\ No newline at end of file
+ saleGroupFk = 6;
+
+
+INSERT INTO account.sambaConfig (id,adDomain,adController,adUser,adPassword,verifyCert,userDn,groupDn) VALUES
+ (1,'verdnatura.es','192.168.56.101','Administrator','V3rdn4tur4$',0,'ou=VnUsers','ou=VnGroups');
diff --git a/db/versions/11035-blueGalax/00-firstScript.sql b/db/versions/11035-blueGalax/00-firstScript.sql
new file mode 100644
index 000000000..4ba206d81
--- /dev/null
+++ b/db/versions/11035-blueGalax/00-firstScript.sql
@@ -0,0 +1,2 @@
+-- Place your SQL code here
+ALTER TABLE account.sambaConfig ADD groupDn varchar(255) NULL COMMENT 'The base DN for groups';
diff --git a/modules/account/back/methods/account-linker/syncRoles.js b/modules/account/back/methods/account-linker/syncRoles.js
new file mode 100644
index 000000000..682664bcf
--- /dev/null
+++ b/modules/account/back/methods/account-linker/syncRoles.js
@@ -0,0 +1,17 @@
+const NotFoundError = require('vn-loopback/util/not-found-error');
+
+module.exports = Self => {
+ Self.remoteMethod('syncRoles', {
+ description: 'syncRoles',
+ http: {
+ path: `/syncRoles`,
+ verb: 'GET'
+ }
+ });
+
+ Self.syncRoles = async function() {
+ const connector = await Self.getLinker();
+ if (!connector) throw new NotFoundError('Linker not configured');
+ await connector.syncRoles();
+ };
+};
diff --git a/modules/account/back/mixins/account-linker.js b/modules/account/back/mixins/account-linker.js
index c882d0893..d9871ddeb 100644
--- a/modules/account/back/mixins/account-linker.js
+++ b/modules/account/back/mixins/account-linker.js
@@ -4,6 +4,7 @@ const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self, options) {
require('../methods/account-linker/test')(Self);
+ require('../methods/account-linker/syncRoles')(Self);
Self.once('attached', function() {
app.models.AccountConfig.addLinker(Self);
@@ -54,7 +55,15 @@ module.exports = function(Self, options) {
/**
* Synchronizes roles.
*/
- async syncRoles() {},
+ async syncRoles() {
+ try {
+ await this.syncRoles();
+ } catch (e) {
+ let err = new UserError(e.message);
+ err.name = e.name;
+ throw err;
+ }
+ },
/**
* Tests linker configuration.
diff --git a/modules/account/back/models/samba-config.js b/modules/account/back/models/samba-config.js
index 359b4b187..db5f37f87 100644
--- a/modules/account/back/models/samba-config.js
+++ b/modules/account/back/models/samba-config.js
@@ -1,6 +1,6 @@
const ldap = require('../util/ldapjs-extra');
-const execFile = require('child_process').execFile;
+const SambaHelper = require('./samba-helper');
const isProduction = require('vn-loopback/server/boot/isProduction');
/**
@@ -24,6 +24,7 @@ module.exports = Self => {
'adUser',
'adPassword',
'userDn',
+ 'groupDn',
'verifyCert'
]
});
@@ -46,38 +47,16 @@ module.exports = Self => {
Object.assign(this, {
adClient,
fullUsersDn: `${this.userDn},${baseDn}`,
+ fullGroupsDn: `${this.groupDn},${baseDn}`,
bindDn
});
+ this.sambaHandler = new SambaHelper(this);
},
async deinit() {
await this.adClient.unbind();
},
- async sambaTool(command, args = []) {
- let authArgs = [
- '--URL', `ldaps://${this.adController}`,
- '--simple-bind-dn', this.bindDn,
- '--password', this.adPassword
- ];
- if (!this.verifyCert)
- authArgs.push('--option', 'tls verify peer = no_check');
-
- 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});
- });
- });
- },
-
async getAdUser(userName) {
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
scope: 'sub',
@@ -105,7 +84,7 @@ module.exports = Self => {
if (info.hasAccount) {
if (!sambaUser) {
- await this.sambaTool('user', [
+ await this.sambaHandler.sambaTool('user', [
'create', userName,
'--userou', this.userDn,
'--random-password'
@@ -113,7 +92,7 @@ module.exports = Self => {
sambaUser = await this.getAdUser(userName);
}
if (password) {
- await this.sambaTool('user', [
+ await this.sambaHandler.sambaTool('user', [
'setpassword', userName,
'--newpassword', password
]);
@@ -169,6 +148,17 @@ module.exports = Self => {
};
await this.adClient.searchForeach(this.fullUsersDn, opts,
o => usersToSync.add(o.sAMAccountName));
- }
+ },
+ async syncRoles() {
+ await this.init();
+ // Prepare data
+ try {
+ await this.sambaHandler.syncFromDB();
+ await this.sambaHandler.syncMembers();
+ } catch (error) {
+ console.error(error);
+ }
+ },
});
};
+
diff --git a/modules/account/back/models/samba-config.json b/modules/account/back/models/samba-config.json
index 4c9e0a794..6fde4f97a 100644
--- a/modules/account/back/models/samba-config.json
+++ b/modules/account/back/models/samba-config.json
@@ -32,6 +32,10 @@
"type": "string",
"required": true
},
+ "groupDn": {
+ "type": "string",
+ "required": true
+ },
"verifyCert": {
"type": "boolean"
}
diff --git a/modules/account/back/models/samba-helper.js b/modules/account/back/models/samba-helper.js
new file mode 100644
index 000000000..713ed71c7
--- /dev/null
+++ b/modules/account/back/models/samba-helper.js
@@ -0,0 +1,257 @@
+const {differences, printResults} = require('../util/helpers');
+const ldap = require('../util/ldapjs-extra');
+
+const ROLE_PREFIX = '$';
+const app = require('vn-loopback/server/server');
+
+module.exports = class SambaHelper {
+ constructor(ctx) {
+ const {verifyCert, adPassword, adController, groupDn, userDn} = ctx;
+ Object.assign(this, {...ctx, verifyCert, adPassword, adController, groupDn, userDn});
+ }
+
+ async sambaTool(command, args = []) {
+ let authArgs = [
+ '--URL', `ldaps://${this.adController}`,
+ '--simple-bind-dn', this.bindDn,
+ '--password', this.adPassword
+ ];
+ if (!this.verifyCert)
+ authArgs.push('--option', 'tls verify peer = no_check');
+
+ 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});
+ });
+ });
+ }
+
+ async getRoles() {
+ this.roles = (await app.models.VnRole.find({
+ fields: ['id', 'name', 'description'],
+ order: 'modified DESC',
+ limit: 2
+ })).reduce((map, role) => {
+ map.set(`${ROLE_PREFIX}${role.name}`, role);
+ return map;
+ }, new Map());
+ }
+
+ async getUsers() {
+ this.users = await app.models.VnUser.find({
+ include: {
+ relation: 'role',
+ scope: {fields: ['name'],
+ where: {'name': {nin: this.rolesToDelete}}
+ }
+ },
+ fields: ['name', 'roleFk'],
+ // where: {'active': true}
+ }).reduce((map, user) => {
+ const role = user.role();
+ const roleName = `${ROLE_PREFIX}${role.name}`;
+ if (map.has(roleName))
+ map.get(roleName).push(user.name);
+ else {
+ // Si no existe, creamos una nueva lista con el nombre del usuario y la asociamos a la clave
+ map.set(roleName, [user.name]);
+ }
+ return map;
+ }, new Map());
+ // this.usersMap = toMap(this.users, user => {
+ // let role = user.role();
+ // if (!role) {
+ // console.info(`User ${user.name} has not valid role`);
+ // return;
+ // }
+ // return {key: `${ROLE_PREFIX}${role.name}`, val: user.name};
+ // });
+ }
+ deleteRole(role) {
+ return this.sambaTool('group', ['delete', role]);
+ }
+
+ addRole({description, name}) {
+ return this.sambaTool('group',
+ ['add', `${ROLE_PREFIX}${name}`, `--groupou=${this.groupDn}`, `--description=${description}`]);
+ }
+
+ getRoleMembers(role) {
+ return this.getMembers(`(cn=${role})`, this.fullGroupsDn);
+ }
+
+ getMembers(filter = '', type = this.fullUsersDn) {
+ const options = {
+ scope: 'sub',
+ attributes: ['cn', 'member', 'member.cn']
+ };
+ if (filter !== '')
+ Object.assign(options, {filter});
+ return this.adClient.searchAll(type, options);
+ }
+
+ removeMembers(role, user) {
+ return this.sambaTool('group', ['removemembers', role, user]);
+ }
+
+ addMembers(role, user) {
+ return this.sambaTool('group', ['addmembers', role, user]);
+ }
+
+ handleRoleMembers(users) {
+ if (users.length === 0) return [];
+ let members = users[0]?.member;
+ if (!members) return [];
+ if (!Array.isArray(members))members = [members];
+ return members.map((member => member.match(/CN=(.*?),(.*)/)[1]));
+ }
+
+ async handleUsersRole(role, currentUsers, users) {
+ const usersToDelete = differences(currentUsers, users);
+ if (usersToDelete.length > 0) {
+ const results = await Promise.all(usersToDelete.map(user =>
+ this.removeMembers(role, user)));
+ printResults(results);
+ }
+ const usersToInsert = differences(users, currentUsers);
+ if (usersToInsert.length > 0) {
+ const results = await Promise.all(usersToInsert.map(user =>
+ this.addMembers(role, user)));
+ printResults(results);
+ }
+ }
+
+ notFilterMap(map) {
+ return ([clave, valor]) => !map.has(clave);
+ }
+ filterMap(map) {
+ return ([clave, valor]) => map.has(clave);
+ }
+
+ getKeysMap(map) {
+ return Array.from(map.keys());
+ }
+
+ async syncFromDB() {
+ // await this.adClient.del(
+ // `cn=developer,${this.fullGroupsDn}`,
+
+ // );
+ // OBTENER ROLES
+ await this.getRoles();
+ // const rolesKeys = Array.from(this.roles.keys()).sort();
+ // OBTENER LDAPSJS ROLES
+ const ldapGroups = await this.adClient.searchAll(this.fullGroupsDn, {
+ scope: 'sub',
+ attributes: ['cn', 'description'],
+ });
+
+ // OBTENER SAMBA ROLES
+ let sambaCurrentGroups = ldapGroups
+ .filter(group => Object.prototype.hasOwnProperty.call(group, 'cn'))
+ .reduce((map, group) => {
+ map.set(`${group.cn}`, group);
+ return map;
+ }, new Map());
+ // const sambaRolesKeys = Array.from(sambaCurrentGroups.keys()).sort();// .map(({cn}) => cn);
+ // handleExecResponse(await this.sambaTool('group', ['list']))
+ // .filter(group => group.startsWith(ROLE_PREFIX));
+
+ // Encontrar elementos a insertar
+ this.rolesToInsert = this.getKeysMap(new Map([...this.roles].filter(this.notFilterMap(sambaCurrentGroups))));
+ // Encontrar elementos a actualizar
+ this.rolesToUpdate = this.getKeysMap(new Map([...this.roles].filter(this.filterMap(sambaCurrentGroups))));
+ // Encontrar elementos a eliminar
+ this.rolesToDelete = this.getKeysMap(new Map([...sambaCurrentGroups].filter(this.notFilterMap(this.roles))));
+
+ if (this.rolesToDelete.length > 0) {
+ // PROCEDIMIENTO PARA ELIMINAR ROLES
+ const resultsRoleDelete = await Promise.all(
+ this.rolesToDelete.map(this.deleteRole)
+ );
+ printResults(resultsRoleDelete);
+ }
+
+ if (this.rolesToInsert.length > 0) {
+ // PROCEDIMIENTO PARA INSERTAR ROLES
+ const resultsRoleInsert = await Promise.all(
+ this.rolesToInsert.map(role => this.addRole(this.roles.get(role))));
+ printResults(resultsRoleInsert);
+ }
+
+ if (this.rolesToUpdate.length > 0) {
+ for await (const role of this.rolesToUpdate) {
+ if (this.roles.get(role).$description != sambaCurrentGroups.get(role).description) {
+ const groupDescriptionAttribute = {
+ description: this.roles.get(role).$description,
+ };
+ const changed = await this.adClient.modify(
+ `cn=${role},${this.fullGroupsDn}`,
+ new ldap.Change({
+ operation: 'replace',
+ modification: groupDescriptionAttribute,
+ })
+ );
+ console.log(changed);
+ // await this.deleteRole(role);
+ // await this.addRole(this.roles.get(role));
+ }
+ }
+ }
+ }
+ async syncMembers() {
+ // const baseDN = 'cn=Users,dc=verdnatura,dc=es';
+ const ldapMembersGroups = await this.getMembers();
+ // OBTENER USUARIOS Y SUS ROLES
+ if (
+ this.rolesToInsert.length > 0 ||
+ this.rolesToUpdate.length > 0)
+ await this.getUsers();
+
+ // PROCEDIMIENTO PARA ELIMINAR USUARIOS ASOCIADOS AL ROL
+ // if (this.rolesToDelete.length > 0) {
+ // let usersToUngroup = this.rolesToDelete.flatMap(role => {
+ // const exist = this.users.get(role);
+
+ // if (exist) {
+ // return this.users.get(role)?.map(
+ // user => this.removeMembers(role, user)
+ // );
+ // } else return [];
+ // }
+ // );
+ // const resultsUsersUngroup = await Promise.all(usersToUngroup);
+ // printResults(resultsUsersUngroup);
+ // }
+ if (this.rolesToInsert.length > 0) {
+ // PROCEDIMIENTO PARA INSERTAR USUARIOS ASOCIADOS AL ROL
+ let usersToGroup = this.rolesToInsert.flatMap(role => this.users.get(role).map(
+ user => this.addMembers(role, user)
+ )
+ );
+ const resultsUserGroup = await Promise.all(usersToGroup);
+ printResults(resultsUserGroup);
+ }
+
+ if (this.rolesToUpdate.length > 0) {
+ // OBTENER LDAPSJS MIEMBROS ROLES
+
+ const forbiddenUsers = ['$guest'];
+ this.rolesToUpdate = this.rolesToUpdate.filter(role => !forbiddenUsers.includes(role));
+ for await (const role of this.rolesToUpdate) {
+ const users = this.users.get(role);
+ const currentUsers = this.handleRoleMembers(await this.getRoleMembers(role));
+ if (currentUsers.length === 0 && users.length === 0) continue;
+ await this.handleUsersRole(role, currentUsers, users);
+ }
+ }
+ }
+};
diff --git a/modules/account/back/models/specs/samba-config.spec.js b/modules/account/back/models/specs/samba-config.spec.js
new file mode 100644
index 000000000..a1d780a6c
--- /dev/null
+++ b/modules/account/back/models/specs/samba-config.spec.js
@@ -0,0 +1,6 @@
+
+xdescribe('Samba config', () => {
+ it('SyncRoles', async() => {
+
+ });
+});
diff --git a/modules/account/back/util/helpers.js b/modules/account/back/util/helpers.js
new file mode 100644
index 000000000..0e5ad9b9c
--- /dev/null
+++ b/modules/account/back/util/helpers.js
@@ -0,0 +1,70 @@
+module.exports = {
+ toMap,
+ binarySearch,
+ differences,
+ printResults,
+ handleExecResponse
+};
+
+function handleExecResponse({stdin, stdout}) {
+ return stdout.split('\n');
+}
+function printResults(results) {
+ // eslint-disable-next-line no-console
+ results.forEach(({stdout}) => console.log(stdout));
+}
+
+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;
+}
+function binarySearch(array, value) {
+ let first = 0;
+
+ let last = array.length - 1;
+
+ while (first <= last) {
+ const index = Math.floor((first + last) / 2);
+
+ const middle = array[index];
+
+ if (middle === value)
+ return index; // Elemento encontrado, devuelve la posición.
+
+ if (middle < value)
+ first = index + 1;
+ else
+ last = index - 1;
+ }
+
+ return -1; // Elemento no encontrado.
+}
+
+function differences(array1, array2) {
+ const differences = [];
+
+ // Ordena ambos arrays
+
+ const sortedArray1 = array1.slice().sort();
+ const sortedArray2 = array2.slice().sort();
+
+ for (const value of sortedArray1) {
+ // Busca el elemento en el array ordenado utilizando búsqueda binaria
+
+ const result = binarySearch(sortedArray2, value);
+
+ // Si el elemento no se encuentra, agrégalo a la lista de diferencias
+
+ if (result === -1)
+ differences.push(value);
+ }
+
+ return differences;
+}
diff --git a/modules/account/front/samba/index.html b/modules/account/front/samba/index.html
index 0186cac7c..37501592e 100644
--- a/modules/account/front/samba/index.html
+++ b/modules/account/front/samba/index.html
@@ -45,6 +45,11 @@
ng-model="$ctrl.config.userDn"
rule="SambaConfig">
+
+