WIP: #5770 - Sync Groups Samba #1946
|
@ -79,8 +79,8 @@ INSERT INTO `account`.`roleConfig`(`id`, `mysqlPassword`, `rolePrefix`, `userPre
|
||||||
|
|
||||||
CALL `account`.`role_sync`;
|
CALL `account`.`role_sync`;
|
||||||
|
|
||||||
INSERT INTO `account`.`user`(`id`,`name`, `nickname`, `role`,`active`,`email`, `lang`, `image`, `password`)
|
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'
|
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`
|
FROM `account`.`role`
|
||||||
ORDER BY id;
|
ORDER BY id;
|
||||||
|
|
||||||
|
@ -3856,3 +3856,7 @@ INSERT INTO vn.sectorCollectionSaleGroup
|
||||||
SET id = 9,
|
SET id = 9,
|
||||||
sectorCollectionFk = 3,
|
sectorCollectionFk = 3,
|
||||||
saleGroupFk = 6;
|
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');
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- Place your SQL code here
|
||||||
|
ALTER TABLE account.sambaConfig ADD groupDn varchar(255) NULL COMMENT 'The base DN for groups';
|
|
@ -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();
|
||||||
|
};
|
||||||
|
};
|
|
@ -4,6 +4,7 @@ const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = function(Self, options) {
|
module.exports = function(Self, options) {
|
||||||
require('../methods/account-linker/test')(Self);
|
require('../methods/account-linker/test')(Self);
|
||||||
|
require('../methods/account-linker/syncRoles')(Self);
|
||||||
|
|
||||||
Self.once('attached', function() {
|
Self.once('attached', function() {
|
||||||
app.models.AccountConfig.addLinker(Self);
|
app.models.AccountConfig.addLinker(Self);
|
||||||
|
@ -54,7 +55,15 @@ module.exports = function(Self, options) {
|
||||||
/**
|
/**
|
||||||
* Synchronizes roles.
|
* 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.
|
* Tests linker configuration.
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
const ldap = require('../util/ldapjs-extra');
|
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');
|
const isProduction = require('vn-loopback/server/boot/isProduction');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,7 @@ module.exports = Self => {
|
||||||
'adUser',
|
'adUser',
|
||||||
'adPassword',
|
'adPassword',
|
||||||
'userDn',
|
'userDn',
|
||||||
|
'groupDn',
|
||||||
'verifyCert'
|
'verifyCert'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -46,38 +47,16 @@ module.exports = Self => {
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
adClient,
|
adClient,
|
||||||
fullUsersDn: `${this.userDn},${baseDn}`,
|
fullUsersDn: `${this.userDn},${baseDn}`,
|
||||||
|
fullGroupsDn: `${this.groupDn},${baseDn}`,
|
||||||
bindDn
|
bindDn
|
||||||
});
|
});
|
||||||
|
this.sambaHandler = new SambaHelper(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
async deinit() {
|
async deinit() {
|
||||||
await this.adClient.unbind();
|
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) {
|
async getAdUser(userName) {
|
||||||
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
|
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
|
@ -105,7 +84,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
if (info.hasAccount) {
|
if (info.hasAccount) {
|
||||||
if (!sambaUser) {
|
if (!sambaUser) {
|
||||||
await this.sambaTool('user', [
|
await this.sambaHandler.sambaTool('user', [
|
||||||
'create', userName,
|
'create', userName,
|
||||||
'--userou', this.userDn,
|
'--userou', this.userDn,
|
||||||
'--random-password'
|
'--random-password'
|
||||||
|
@ -113,7 +92,7 @@ module.exports = Self => {
|
||||||
sambaUser = await this.getAdUser(userName);
|
sambaUser = await this.getAdUser(userName);
|
||||||
}
|
}
|
||||||
if (password) {
|
if (password) {
|
||||||
await this.sambaTool('user', [
|
await this.sambaHandler.sambaTool('user', [
|
||||||
'setpassword', userName,
|
'setpassword', userName,
|
||||||
'--newpassword', password
|
'--newpassword', password
|
||||||
]);
|
]);
|
||||||
|
@ -169,6 +148,17 @@ module.exports = Self => {
|
||||||
};
|
};
|
||||||
await this.adClient.searchForeach(this.fullUsersDn, opts,
|
await this.adClient.searchForeach(this.fullUsersDn, opts,
|
||||||
o => usersToSync.add(o.sAMAccountName));
|
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);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,10 @@
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"required": true
|
"required": true
|
||||||
},
|
},
|
||||||
|
"groupDn": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"verifyCert": {
|
"verifyCert": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,6 @@
|
||||||
|
|
||||||
|
xdescribe('Samba config', () => {
|
||||||
|
it('SyncRoles', async() => {
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
|
@ -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;
|
||||||
|
}
|
|
@ -45,6 +45,11 @@
|
||||||
ng-model="$ctrl.config.userDn"
|
ng-model="$ctrl.config.userDn"
|
||||||
rule="SambaConfig">
|
rule="SambaConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Group DN (without domain part)"
|
||||||
|
ng-model="$ctrl.config.groupDn"
|
||||||
|
rule="SambaConfig">
|
||||||
|
</vn-textfield>
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Verify certificate"
|
label="Verify certificate"
|
||||||
ng-model="$ctrl.config.verifyCert">
|
ng-model="$ctrl.config.verifyCert">
|
||||||
|
|
Loading…
Reference in New Issue