Merge pull request 'test' (!1863) from test into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #1863
This commit is contained in:
Juan Ferrer 2023-11-27 12:17:11 +00:00
commit c7291e3367
16 changed files with 110 additions and 116 deletions

View File

@ -1,3 +1,4 @@
const NotFoundError = require('vn-loopback/util/not-found-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('test', { Self.remoteMethod('test', {
@ -9,7 +10,8 @@ module.exports = Self => {
}); });
Self.test = async function() { Self.test = async function() {
let connector = await Self.getSynchronizer(); const connector = await Self.getLinker();
if (!connector) throw new NotFoundError('Linker not configured');
await connector.test(); await connector.test();
}; };
}; };

View File

@ -3,14 +3,14 @@ const app = require('vn-loopback/server/server');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = function(Self, options) { module.exports = function(Self, options) {
require('../methods/account-synchronizer/test')(Self); require('../methods/account-linker/test')(Self);
Self.once('attached', function() { Self.once('attached', function() {
app.models.AccountConfig.addSynchronizer(Self); app.models.AccountConfig.addLinker(Self);
}); });
/** /**
* Mixin for user synchronizers. * Mixin for account linkers.
* *
* @property {Array<Model>} $ * @property {Array<Model>} $
* @property {Object} accountConfig * @property {Object} accountConfig
@ -18,12 +18,12 @@ module.exports = function(Self, options) {
*/ */
let Mixin = { let Mixin = {
/** /**
* Initalizes the synchronizer. * Initalizes the linker.
*/ */
async init() {}, async init() {},
/** /**
* Deinitalizes the synchronizer. * Deinitalizes the linker.
*/ */
async deinit() {}, async deinit() {},
@ -57,7 +57,7 @@ module.exports = function(Self, options) {
async syncRoles() {}, async syncRoles() {},
/** /**
* Tests synchronizer configuration. * Tests linker configuration.
*/ */
async test() { async test() {
try { try {

View File

@ -3,94 +3,85 @@ const models = require('vn-loopback/server/server').models;
module.exports = Self => { module.exports = Self => {
Object.assign(Self, { Object.assign(Self, {
synchronizers: [], linkers: [],
addSynchronizer(synchronizer) { addLinker(linker) {
this.synchronizers.push(synchronizer); this.linkers.push(linker);
}, },
async getInstance() { async initEngine() {
let instance = await Self.findOne({ const accountConfig = await Self.findOne({
fields: ['homedir', 'shell', 'idBase'] fields: ['homedir', 'shell', 'idBase']
}); });
await instance.synchronizerInit(); const mailConfig = await models.MailConfig.findOne({
return instance; fields: ['domain']
});
const linkers = [];
for (const Linker of Self.linkers) {
const linker = await Linker.getLinker();
if (!linker) continue;
Object.assign(linker, {accountConfig});
await linker.init();
linkers.push(linker);
}
Object.assign(accountConfig, {
linkers,
domain: mailConfig.domain
});
return {
accountConfig,
linkers
};
},
async deinitEngine(engine) {
for (const linker of engine.linkers)
await linker.deinit();
},
async syncUser(userName, password) {
const engine = await Self.initEngine();
try {
await Self.syncUserBase(engine, userName, password, true);
} finally {
await Self.deinitEngine(engine);
}
}, },
async syncUsers() { async syncUsers() {
let instance = await Self.getInstance(); const engine = await Self.initEngine();
let usersToSync = new Set();
for (const linker of engine.linkers)
await linker.getUsers(usersToSync);
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));
for (let userName of usersToSync) { for (let userName of usersToSync) {
try { try {
// eslint-disable-next-line no-console
console.log(`Synchronizing user '${userName}'`); console.log(`Synchronizing user '${userName}'`);
await instance.synchronizerSyncUser(userName);
await Self.syncUserBase(engine, userName);
// eslint-disable-next-line no-console
console.log(` -> User '${userName}' sinchronized`); console.log(` -> User '${userName}' sinchronized`);
} catch (err) { } catch (err) {
// eslint-disable-next-line no-console
console.error(` -> User '${userName}' synchronization error:`, err.message); console.error(` -> User '${userName}' synchronization error:`, err.message);
} }
} }
await instance.synchronizerDeinit(); await Self.deinitEngine(engine);
await Self.syncRoles(); await Self.syncRoles();
}, },
async syncUser(userName, password) { async syncUserBase(engine, userName, password, syncGroups) {
let instance = await Self.getInstance();
try {
await instance.synchronizerSyncUser(userName, password, true);
} finally {
await instance.synchronizerDeinit();
}
},
async syncRoles() {
let instance = await Self.getInstance();
try {
await instance.synchronizerSyncRoles();
} finally {
await instance.synchronizerDeinit();
}
},
async getSynchronizer() {
return await Self.findOne();
}
});
Object.assign(Self.prototype, {
async synchronizerInit() {
let mailConfig = await models.MailConfig.findOne({
fields: ['domain']
});
let synchronizers = [];
for (let Synchronizer of Self.synchronizers) {
let synchronizer = await Synchronizer.getSynchronizer();
if (!synchronizer) continue;
Object.assign(synchronizer, {
accountConfig: this
});
await synchronizer.init();
synchronizers.push(synchronizer);
}
Object.assign(this, {
synchronizers,
domain: mailConfig.domain
});
},
async synchronizerDeinit() {
for (let synchronizer of this.synchronizers)
await synchronizer.deinit();
},
async synchronizerSyncUser(userName, password, syncGroups) {
if (!userName) return; if (!userName) return;
userName = userName.toLowerCase(); userName = userName.toLowerCase();
@ -98,7 +89,7 @@ module.exports = Self => {
if (['administrator', 'root'].indexOf(userName) >= 0) if (['administrator', 'root'].indexOf(userName) >= 0)
return; return;
let user = await models.VnUser.findOne({ const user = await models.VnUser.findOne({
where: {name: userName}, where: {name: userName},
fields: [ fields: [
'id', 'id',
@ -130,27 +121,28 @@ module.exports = Self => {
] ]
}); });
let info = { const info = {
user, user,
hasAccount: false hasAccount: false
}; };
if (user) { if (user) {
let exists = await models.Account.exists(user.id); const exists = await models.Account.exists(user.id);
const {accountConfig} = engine;
Object.assign(info, { Object.assign(info, {
hasAccount: user.active && exists, hasAccount: user.active && exists,
corporateMail: `${userName}@${this.domain}`, corporateMail: `${userName}@${accountConfig.domain}`,
uidNumber: this.idBase + user.id uidNumber: accountConfig.idBase + user.id
}); });
} }
let errs = []; const errs = [];
for (let synchronizer of this.synchronizers) { for (const linker of engine.linkers) {
try { try {
await synchronizer.syncUser(userName, info, password); await linker.syncUser(userName, info, password);
if (syncGroups) if (syncGroups)
await synchronizer.syncUserGroups(userName, info); await linker.syncUserGroups(userName, info);
} catch (err) { } catch (err) {
errs.push(err); errs.push(err);
} }
@ -159,18 +151,16 @@ module.exports = Self => {
if (errs.length) throw errs[0]; if (errs.length) throw errs[0];
}, },
async synchronizerGetUsers() { async syncRoles() {
let usersToSync = new Set(); const engine = await Self.initEngine();
try {
await Self.rawSql(`CALL account.role_sync`);
for (let synchronizer of this.synchronizers) for (const linker of engine.linkers)
await synchronizer.getUsers(usersToSync); await linker.syncRoles();
} finally {
return usersToSync; await Self.deinitEngine(engine);
}, }
async synchronizerSyncRoles() {
for (let synchronizer of this.synchronizers)
await synchronizer.syncRoles();
} }
}); });
}; };

View File

@ -7,7 +7,7 @@ const nthash = require('smbhash').nthash;
module.exports = Self => { module.exports = Self => {
const shouldSync = process.env.NODE_ENV !== 'test'; const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() { Self.getLinker = async function() {
return await Self.findOne({ return await Self.findOne({
fields: [ fields: [
'server', 'server',
@ -24,6 +24,7 @@ module.exports = Self => {
this.client = ldap.createClient({ this.client = ldap.createClient({
url: this.server url: this.server
}); });
this.client.on('error', () => {});
await this.client.bind(this.rdn, this.password); await this.client.bind(this.rdn, this.password);
}, },

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {

View File

@ -1,6 +1,6 @@
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { Self.getLinker = async function() {
let NODE_ENV = process.env.NODE_ENV; let NODE_ENV = process.env.NODE_ENV;
if (!NODE_ENV || NODE_ENV == 'development') if (!NODE_ENV || NODE_ENV == 'development')
return null; return null;
@ -45,6 +45,7 @@ module.exports = Self => {
} }
if (!isUpdatable) { if (!isUpdatable) {
// eslint-disable-next-line no-console
console.warn(`RoleConfig.syncUser(): User '${userName}' cannot be updated, not managed by me`); console.warn(`RoleConfig.syncUser(): User '${userName}' cannot be updated, not managed by me`);
return; return;
} }
@ -82,6 +83,7 @@ module.exports = Self => {
[mysqlUser, this.userHost]); [mysqlUser, this.userHost]);
} catch (err) { } catch (err) {
if (err.code == 'ER_REVOKE_GRANTS') if (err.code == 'ER_REVOKE_GRANTS')
// eslint-disable-next-line no-console
console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`); console.warn(`${err.code}: ${err.sqlMessage}: ${err.sql}`);
else else
throw err; throw err;

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {

View File

@ -9,7 +9,7 @@ module.exports = Self => {
Self.observe(hook, async() => { Self.observe(hook, async() => {
try { try {
await Self.rawSql(` await Self.rawSql(`
CREATE EVENT account.role_sync CREATE DEFINER = CURRENT_ROLE EVENT account.role_sync
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 5 SECOND
DO CALL role_sync; DO CALL role_sync;
`); `);

View File

@ -13,7 +13,7 @@ const UserAccountControlFlags = {
module.exports = Self => { module.exports = Self => {
const shouldSync = process.env.NODE_ENV !== 'test'; const shouldSync = process.env.NODE_ENV !== 'test';
Self.getSynchronizer = async function() { Self.getLinker = async function() {
return await Self.findOne({ return await Self.findOne({
fields: [ fields: [
'host', 'host',
@ -39,6 +39,7 @@ module.exports = Self => {
url: `ldaps://${this.adController}:636`, url: `ldaps://${this.adController}:636`,
tlsOptions: {rejectUnauthorized: this.verifyCert} tlsOptions: {rejectUnauthorized: this.verifyCert}
}); });
adClient.on('error', () => {});
await adClient.bind(bindDn, this.adPassword); await adClient.bind(bindDn, this.adPassword);
Object.assign(this, { Object.assign(this, {
adClient, adClient,

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {

View File

@ -2,7 +2,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
module.exports = Self => { module.exports = Self => {
Self.getSynchronizer = async function() { Self.getLinker = async function() {
return await Self.findOne({fields: ['id']}); return await Self.findOne({fields: ['id']});
}; };

View File

@ -7,7 +7,7 @@
} }
}, },
"mixins": { "mixins": {
"AccountSynchronizer": {} "AccountLinker": {}
}, },
"properties": { "properties": {
"id": { "id": {
@ -16,4 +16,3 @@
} }
} }
} }

View File

@ -68,7 +68,6 @@
Deactivate user Deactivate user
</vn-item> </vn-item>
<vn-item <vn-item
ng-if="$ctrl.user.active"
ng-click="syncUser.show()" ng-click="syncUser.show()"
name="synchronizeUser" name="synchronizeUser"
vn-acl="it" vn-acl="it"
@ -166,7 +165,7 @@
vn-id="syncUser" vn-id="syncUser"
on-accept="$ctrl.onSync()" on-accept="$ctrl.onSync()"
on-close="$ctrl.onSyncClose()"> on-close="$ctrl.onSyncClose()">
<tpl-title ng-translate> <tpl-title translate>
Do you want to synchronize user? Do you want to synchronize user?
</tpl-title> </tpl-title>
<tpl-body> <tpl-body>

View File

@ -70,17 +70,18 @@
fields="['started', 'ended']" fields="['started', 'ended']"
ng-model="$ctrl.businessId" ng-model="$ctrl.businessId"
search-function="{businessFk: $search}" search-function="{businessFk: $search}"
show-field="businessFk"
value-field="businessFk" value-field="businessFk"
order="businessFk DESC" order="businessFk DESC"
limit="5"> limit="5">
<tpl-item> <tpl-item>
<div>#{{businessFk}}</div> <div>#{{businessFk}}</div>
<div class="text-caption text-secondary"> <div class="text-caption text-secondary">
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}} {{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
</div> </div>
</tpl-item> </tpl-item>
</vn-autocomplete> </vn-autocomplete>
</div> </div>
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;"> <div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)"> <vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)">
<vn-avatar ng-style="{backgroundColor: absenceType.rgb}"> <vn-avatar ng-style="{backgroundColor: absenceType.rgb}">
@ -110,3 +111,4 @@
message="This item will be deleted" message="This item will be deleted"
question="Are you sure you want to continue?"> question="Are you sure you want to continue?">
</vn-confirm> </vn-confirm>

View File

@ -111,10 +111,8 @@ class Controller extends Section {
dayIndex.setDate(dayIndex.getDate() + 1); dayIndex.setDate(dayIndex.getDate() + 1);
} }
if (this.worker) { this.fetchHours();
this.fetchHours(); this.getWeekData();
this.getWeekData();
}
} }
set weekTotalHours(totalHours) { set weekTotalHours(totalHours) {

View File

@ -14,7 +14,7 @@ module.exports = Self => {
async function doCalc(ctx) { async function doCalc(ctx) {
try { try {
await Self.rawSql(` await Self.rawSql(`
CREATE EVENT zoneClosure_doRecalc CREATE DEFINER = CURRENT_ROLE EVENT zoneClosure_doRecalc
ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 15 SECOND ON SCHEDULE AT CURRENT_TIMESTAMP + INTERVAL 15 SECOND
DO CALL zoneClosure_recalc; DO CALL zoneClosure_recalc;
`); `);