Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2538-supplier_contact

This commit is contained in:
Carlos Jimenez Ruiz 2020-11-02 11:14:20 +01:00
commit ffaf93ab83
29 changed files with 38170 additions and 3707 deletions

View File

@ -9,7 +9,7 @@
"properties": {
"id": {
"type": "number",
"required": true
"id": true
},
"name": {
"type": "string",
@ -56,7 +56,8 @@
"roles": {
"type": "hasMany",
"model": "RoleRole",
"foreignKey": "role"
"foreignKey": "role",
"primaryKey": "roleFk"
},
"emailUser": {
"type": "hasOne",

View File

@ -0,0 +1,7 @@
ALTER TABLE `account`.`roleRole`
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
ADD PRIMARY KEY (`id`);
UPDATE `account`.`role` SET id = 100 WHERE `name` = 'root';
CALL account.role_sync;

11466
front/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -41,7 +41,7 @@ module.exports = Self => {
attributes: ['dn'],
filter: 'objectClass=posixGroup'
};
res = await client.search(ldapConfig.groupDn, opts);
let res = await client.search(ldapConfig.groupDn, opts);
let reqs = [];
await new Promise((resolve, reject) => {
@ -62,31 +62,28 @@ module.exports = Self => {
let roles = await $.Role.find({
fields: ['id', 'name']
});
let roleRoles = await $.RoleRole.find({
fields: ['role', 'inheritsFrom']
});
let roleMap = toMap(roleRoles, e => {
return {key: e.inheritsFrom, val: e.role};
});
let accounts = await $.UserAccount.find({
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
include: {
relation: 'roles',
scope: {
fields: ['inheritsFrom']
}
}
fields: ['name', 'roleFk'],
where: {active: true}
}
}
});
let map = new Map();
for (let account of accounts) {
let user = account.user();
for (let inherit of user.roles()) {
let roleId = inherit.inheritsFrom;
if (!map.has(roleId)) map.set(roleId, []);
map.get(roleId).push(user.name);
}
}
let accountMap = toMap(accounts, e => {
let user = e.user();
if (!user) return;
return {key: user.roleFk, val: user.name};
});
reqs = [];
for (let role of roles) {
@ -96,8 +93,14 @@ module.exports = Self => {
gidNumber: accountConfig.idBase + role.id
};
let memberUid = map.get(role.id);
if (memberUid) newEntry.memberUid = memberUid;
let memberUid = [];
for (subrole of roleMap.get(role.id) || [])
memberUid = memberUid.concat(accountMap.get(subrole) || []);
if (memberUid.length) {
memberUid.sort((a, b) => a.localeCompare(b));
newEntry.memberUid = memberUid;
}
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
reqs.push(client.add(dn, newEntry));
@ -107,8 +110,19 @@ module.exports = Self => {
err = e;
}
// FIXME: Cannot disconnect, hangs on undind() call
// await client.unbind();
await client.unbind();
if (err) throw err;
};
};
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;
}

View File

@ -0,0 +1,37 @@
const SyncEngine = require('../../util/sync-engine');
module.exports = Self => {
Self.remoteMethod('syncAll', {
description: 'Synchronizes user database with LDAP and Samba',
http: {
path: `/syncAll`,
verb: 'PATCH'
}
});
Self.syncAll = async function() {
let $ = Self.app.models;
let se = new SyncEngine();
await se.init($);
let usersToSync = await se.getUsers();
usersToSync = Array.from(usersToSync.values())
.sort((a, b) => a.localeCompare(b));
for (let user of usersToSync) {
try {
console.log(`Synchronizing user '${user}'`);
await se.sync(user);
console.log(` -> '${user}' sinchronized`);
} catch (err) {
console.error(` -> '${user}' synchronization error:`, err.message);
}
}
await se.deinit();
await $.RoleInherit.sync();
};
};

View File

@ -1,7 +1,5 @@
const ldap = require('../../util/ldapjs-extra');
const nthash = require('smbhash').nthash;
const ssh = require('node-ssh');
const crypto = require('crypto');
const SyncEngine = require('../../util/sync-engine');
module.exports = Self => {
Self.remoteMethod('sync', {
@ -19,7 +17,7 @@ module.exports = Self => {
}
],
http: {
path: `/sync`,
path: `/:userName/sync`,
verb: 'PATCH'
}
});
@ -35,233 +33,19 @@ module.exports = Self => {
if (user && isSync) return;
let accountConfig;
let mailConfig;
let extraParams;
let hasAccount = false;
let err;
let se = new SyncEngine();
await se.init($);
if (user) {
accountConfig = await $.AccountConfig.findOne({
fields: ['homedir', 'shell', 'idBase']
});
mailConfig = await $.MailConfig.findOne({
fields: ['domain']
});
user = await $.Account.findById(user.id, {
fields: [
'id',
'nickname',
'email',
'lang',
'roleFk',
'sync',
'active',
'created',
'updated'
],
where: {name: userName},
include: {
relation: 'roles',
scope: {
include: {
relation: 'inherits',
scope: {
fields: ['name']
}
}
}
}
});
extraParams = {
corporateMail: `${userName}@${mailConfig.domain}`,
uidNumber: accountConfig.idBase + user.id
};
hasAccount = user.active
&& await $.UserAccount.exists(user.id);
try {
await se.sync(userName, password, true);
await $.UserSync.destroyById(userName);
} catch (e) {
err = e;
}
if (user) {
let bcryptPassword = $.User.hashPassword(password);
await $.Account.upsertWithWhere({id: user.id},
{bcryptPassword}
);
await $.user.upsert({
id: user.id,
username: userName,
password: bcryptPassword,
email: user.email,
created: user.created,
updated: user.updated
});
}
// SIP
if (hasAccount) {
await Self.rawSql('CALL pbx.sip_setPassword(?, ?)',
[user.id, password]
);
}
// LDAP
let ldapConfig = await $.LdapConfig.findOne({
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
});
if (ldapConfig) {
let ldapClient = ldap.createClient({
url: `ldap://${ldapConfig.host}:389`
});
let ldapPassword = Buffer
.from(ldapConfig.password, 'base64')
.toString('ascii');
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
let err;
try {
// Deletes user
try {
let dn = `uid=${userName},${ldapConfig.baseDn}`;
await ldapClient.del(dn);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;
}
// Removes user from groups
let opts = {
scope: 'sub',
attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
};
res = await ldapClient.search(ldapConfig.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 = [];
for (oldGroup of oldGroups) {
let change = new ldap.Change({
operation: 'delete',
modification: {memberUid: userName}
});
reqs.push(ldapClient.modify(oldGroup.dn, change));
}
await Promise.all(reqs);
if (hasAccount) {
// Recreates user
let nameArgs = user.nickname.split(' ');
let sshaPassword = crypto
.createHash('sha1')
.update(password)
.digest('base64');
let dn = `uid=${userName},${ldapConfig.baseDn}`;
let newEntry = {
uid: userName,
objectClass: [
'inetOrgPerson',
'posixAccount',
'sambaSamAccount'
],
cn: user.nickname || userName,
displayName: user.nickname,
givenName: nameArgs[0],
sn: nameArgs[1] || 'Empty',
mail: extraParams.corporateMail,
userPassword: `{SSHA}${sshaPassword}`,
preferredLanguage: user.lang,
homeDirectory: `${accountConfig.homedir}/${userName}`,
loginShell: accountConfig.shell,
uidNumber: extraParams.uidNumber,
gidNumber: accountConfig.idBase + user.roleFk,
sambaSID: '-',
sambaNTPassword: nthash(password)
};
await ldapClient.add(dn, newEntry);
// Adds user to groups
let reqs = [];
for (let role of user.roles()) {
let change = new ldap.Change({
operation: 'add',
modification: {memberUid: userName}
});
let roleName = role.inherits().name;
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
reqs.push(ldapClient.modify(dn, change));
}
await Promise.all(reqs);
}
} catch (e) {
err = e;
}
// FIXME: Cannot disconnect, hangs on undind() call
// await ldapClient.unbind();
if (err) throw err;
}
// Samba
let sambaConfig = await $.SambaConfig.findOne({
fields: ['host', 'sshUser', 'sshPass']
});
if (sambaConfig) {
let sshPassword = Buffer
.from(sambaConfig.sshPass, 'base64')
.toString('ascii');
let sshClient = new ssh.NodeSSH();
await sshClient.connect({
host: sambaConfig.host,
username: sambaConfig.sshUser,
password: sshPassword
});
let commands;
if (hasAccount) {
commands = [
`samba-tool user create "${userName}" `
+ `--uid-number=${extraParams.uidNumber} `
+ `--mail-address="${extraParams.corporateMail}" `
+ `--random-password`,
`samba-tool user setexpiry "${userName}" `
+ `--noexpiry`,
`samba-tool user setpassword "${userName}" `
+ `--newpassword="${password}"`,
`mkhomedir_helper "${userName}" 0027`
];
} else {
commands = [
`samba-tool user delete "${userName}"`
];
}
for (let command of commands)
await sshClient.execCommand(command);
await sshClient.dispose();
}
// Mark as synchronized
await $.UserSync.destroyById(userName);
await se.deinit();
if (err) throw err;
};
};

View File

@ -5,6 +5,9 @@
"LdapConfig": {
"dataSource": "vn"
},
"Mail": {
"dataSource": "vn"
},
"MailAlias": {
"dataSource": "vn"
},
@ -34,8 +37,5 @@
},
"UserSync": {
"dataSource": "vn"
},
"Mail": {
"dataSource": "vn"
}
}

View File

@ -7,7 +7,7 @@
}
},
"properties": {
"role": {
"id": {
"id": true
}
},

View File

@ -2,4 +2,5 @@
module.exports = Self => {
require('../methods/user-account/sync')(Self);
require('../methods/user-account/sync-by-id')(Self);
require('../methods/user-account/sync-all')(Self);
};

View File

@ -0,0 +1,47 @@
/**
* Base class for user synchronizators.
*
* @property {Array<Model>} $
* @property {Object} accountConfig
* @property {Object} mailConfig
*/
class SyncConnector {
/**
* Initalizes the connector.
*/
async init() {
return true;
}
/**
* Get users to synchronize.
*
* @param {Set} usersToSync Set where users are added
*/
async getUsers(usersToSync) {}
/**
* Synchronizes a user.
*
* @param {Object} info User information
* @param {String} userName The user name
* @param {String} password Thepassword
*/
async sync(info, userName, password) {}
/**
* Synchronizes user groups.
*
* @param {User} user Instace of user
* @param {String} userName The user name
*/
async syncGroups(user, userName) {}
/**
* Deinitalizes the connector.
*/
async deinit() {}
}
SyncConnector.connectors = [];
module.exports = SyncConnector;

View File

@ -0,0 +1,57 @@
const SyncConnector = require('./sync-connector');
class SyncDb extends SyncConnector {
async sync(info, userName, password) {
let {$} = this;
let {user} = info;
if (user && user.active) {
let bcryptPassword = password
? $.User.hashPassword(password)
: user.bcryptPassword;
await $.Account.upsertWithWhere({id: user.id},
{bcryptPassword}
);
let dbUser = {
id: user.id,
username: userName,
email: user.email,
created: user.created,
updated: user.updated
};
if (bcryptPassword)
dbUser.password = bcryptPassword;
if (await $.user.exists(user.id))
await $.user.replaceById(user.id, dbUser);
else
await $.user.create(dbUser);
} else
await $.user.destroyAll({username: userName});
}
async getUsers(usersToSync) {
let accounts = await this.$.UserAccount.find({
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
where: {active: true}
}
}
});
for (let account of accounts) {
let user = account.user();
if (!user) continue;
usersToSync.add(user.name);
}
}
}
SyncConnector.connectors.push(SyncDb);
module.exports = SyncDb;

View File

@ -0,0 +1,127 @@
const SyncConnector = require('./sync-connector');
require('./sync-db');
require('./sync-sip');
require('./sync-ldap');
require('./sync-samba');
module.exports = class SyncEngine {
async init($) {
let accountConfig = await $.AccountConfig.findOne({
fields: ['homedir', 'shell', 'idBase']
});
let mailConfig = await $.MailConfig.findOne({
fields: ['domain']
});
let connectors = [];
for (let ConnectorClass of SyncConnector.connectors) {
let connector = new ConnectorClass();
Object.assign(connector, {
se: this,
$
});
if (!await connector.init()) continue;
connectors.push(connector);
}
Object.assign(this, {
connectors,
$,
accountConfig,
mailConfig
});
}
async deinit() {
for (let connector of this.connectors)
await connector.deinit();
}
async sync(userName, password, syncGroups) {
let {
$,
accountConfig,
mailConfig
} = this;
if (!userName) return;
userName = userName.toLowerCase();
// Skip conflicting users
if (['administrator', 'root'].indexOf(userName) >= 0)
return;
let user = await $.Account.findOne({
where: {name: userName},
fields: [
'id',
'nickname',
'email',
'lang',
'roleFk',
'sync',
'active',
'created',
'bcryptPassword',
'updated'
],
include: {
relation: 'roles',
scope: {
include: {
relation: 'inherits',
scope: {
fields: ['name']
}
}
}
}
});
let extraParams;
let hasAccount = false;
if (user) {
hasAccount = user.active
&& await $.UserAccount.exists(user.id);
extraParams = {
corporateMail: `${userName}@${mailConfig.domain}`,
uidNumber: accountConfig.idBase + user.id
};
}
let info = {
user,
extraParams,
hasAccount,
accountConfig,
mailConfig
};
let errs = [];
for (let connector of this.connectors) {
try {
await connector.sync(info, userName, password);
if (syncGroups)
await connector.syncGroups(info, userName);
} catch (err) {
errs.push(err);
}
}
if (errs.length) throw errs[0];
}
async getUsers() {
let usersToSync = new Set();
for (let connector of this.connectors)
await connector.getUsers(usersToSync);
return usersToSync;
}
};

View File

@ -0,0 +1,204 @@
const SyncConnector = require('./sync-connector');
const nthash = require('smbhash').nthash;
const ldap = require('./ldapjs-extra');
const crypto = require('crypto');
class SyncLdap extends SyncConnector {
async init() {
let ldapConfig = await this.$.LdapConfig.findOne({
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
});
if (!ldapConfig) return false;
let client = ldap.createClient({
url: `ldap://${ldapConfig.host}:389`
});
let ldapPassword = Buffer
.from(ldapConfig.password, 'base64')
.toString('ascii');
await client.bind(ldapConfig.rdn, ldapPassword);
Object.assign(this, {
ldapConfig,
client
});
return true;
}
async deinit() {
if (this.client)
await this.client.unbind();
}
async sync(info, userName, password) {
let {
ldapConfig,
client,
} = this;
let {
user,
hasAccount,
extraParams,
accountConfig
} = info;
let res = await client.search(ldapConfig.baseDn, {
scope: 'sub',
attributes: ['userPassword', 'sambaNTPassword'],
filter: `&(uid=${userName})`
});
let oldUser;
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => oldUser = e.object);
res.on('end', resolve);
});
try {
let dn = `uid=${userName},${ldapConfig.baseDn}`;
await client.del(dn);
} catch (e) {
if (e.name !== 'NoSuchObjectError') throw e;
}
if (!hasAccount) {
if (oldUser)
console.log(` -> '${userName}' removed from LDAP`);
return;
}
let nickname = user.nickname || userName;
let nameArgs = nickname.trim().split(' ');
let sn = nameArgs.length > 1
? nameArgs.splice(1).join(' ')
: '-';
let dn = `uid=${userName},${ldapConfig.baseDn}`;
let newEntry = {
uid: userName,
objectClass: [
'inetOrgPerson',
'posixAccount',
'sambaSamAccount'
],
cn: nickname,
displayName: nickname,
givenName: nameArgs[0],
sn,
mail: extraParams.corporateMail,
preferredLanguage: user.lang,
homeDirectory: `${accountConfig.homedir}/${userName}`,
loginShell: accountConfig.shell,
uidNumber: extraParams.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 syncGroups(info, userName) {
let {
ldapConfig,
client
} = this;
let {
user,
hasAccount
} = info;
let res = await client.search(ldapConfig.groupDn, {
scope: 'sub',
attributes: ['dn'],
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
});
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 = [];
for (let oldGroup of oldGroups) {
let change = new ldap.Change({
operation: 'delete',
modification: {memberUid: userName}
});
reqs.push(client.modify(oldGroup.dn, change));
}
await Promise.all(reqs);
if (!hasAccount) return;
reqs = [];
for (let role of user.roles()) {
let change = new ldap.Change({
operation: 'add',
modification: {memberUid: userName}
});
let roleName = role.inherits().name;
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
reqs.push(client.modify(dn, change));
}
await Promise.all(reqs);
}
async getUsers(usersToSync) {
let {
ldapConfig,
client
} = this;
let res = await client.search(ldapConfig.baseDn, {
scope: 'sub',
attributes: ['uid'],
filter: `uid=*`
});
await new Promise((resolve, reject) => {
res.on('error', reject);
res.on('searchEntry', e => usersToSync.add(e.object.uid));
res.on('end', resolve);
});
}
}
SyncConnector.connectors.push(SyncLdap);
module.exports = SyncLdap;

View File

@ -0,0 +1,93 @@
const SyncConnector = require('./sync-connector');
const ssh = require('node-ssh');
class SyncSamba extends SyncConnector {
async init() {
let sambaConfig = await this.$.SambaConfig.findOne({
fields: ['host', 'sshUser', 'sshPass']
});
if (!sambaConfig) return false;
let sshPassword = Buffer
.from(sambaConfig.sshPass, 'base64')
.toString('ascii');
let client = new ssh.NodeSSH();
await client.connect({
host: sambaConfig.host,
username: sambaConfig.sshUser,
password: sshPassword
});
Object.assign(this, {
sambaConfig,
client
});
return true;
}
async deinit() {
if (this.client)
await this.client.dispose();
}
async sync(info, userName, password) {
let {
client
} = this;
let {
hasAccount,
extraParams
} = info;
if (hasAccount) {
try {
await client.exec('samba-tool user create', [
userName,
'--uid-number', `${extraParams.uidNumber}`,
'--mail-address', extraParams.corporateMail,
'--random-password'
]);
} catch (e) {}
await client.exec('samba-tool user setexpiry', [
userName,
'--noexpiry'
]);
if (password) {
await client.exec('samba-tool user setpassword', [
userName,
'--newpassword', password
]);
await client.exec('samba-tool user enable', [
userName
]);
}
await client.exec('mkhomedir_helper', [
userName,
'0027'
]);
} else {
try {
await client.exec('samba-tool user disable', [
userName
]);
console.log(` -> '${userName}' disabled on Samba`);
} catch (e) {}
}
}
async getUsers(usersToSync) {
let {client} = this;
let res = await client.execCommand('samba-tool user list');
let users = res.stdout.split('\n');
for (let user of users) usersToSync.add(user.trim());
}
}
SyncConnector.connectors.push(SyncSamba);
module.exports = SyncSamba;

View File

@ -0,0 +1,15 @@
const SyncConnector = require('./sync-connector');
class SyncSip extends SyncConnector {
async sync(info, userName, password) {
if (!info.hasAccount || !password) return;
await this.$.Account.rawSql('CALL pbx.sip_setPassword(?, ?)',
[info.user.id, password]
);
}
}
SyncConnector.connectors.push(SyncSip);
module.exports = SyncSip;

View File

@ -15,3 +15,6 @@ import './basic-data';
import './mail-forwarding';
import './aliases';
import './roles';
import './ldap';
import './samba';
import './posix';

View File

@ -0,0 +1,93 @@
<vn-watcher
vn-id="watcher"
url="LdapConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="Host"
ng-model="$ctrl.config.host"
rule="LdapConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="RDN"
ng-model="$ctrl.config.rdn"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.config.password"
info="Password should be base64 encoded"
type="password"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Base DN"
ng-model="$ctrl.config.baseDn"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Filter"
ng-model="$ctrl.config.filter"
rule="LdapConfig">
</vn-textfield>
<vn-textfield
label="Group DN"
ng-model="$ctrl.config.groupDn"
rule="LdapConfig">
</vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
<vn-button
label="Synchronize now"
ng-if="watcher.hasData"
ng-click="$ctrl.onSynchronizeAll()">
</vn-button>
<vn-button
label="Synchronize user"
ng-if="watcher.hasData"
ng-click="syncUser.show()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>
<vn-dialog
vn-id="syncUser"
on-accept="$ctrl.onUserSync()"
on-close="$ctrl.onPassClose()">
<tpl-body>
<vn-textfield
label="Username"
ng-model="$ctrl.syncUser">
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.syncPassword"
type="password"
info="If password is not specified, just user attributes are synchronized">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Synchronize</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,30 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
export default class Controller extends Section {
onSynchronizeAll() {
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
this.$http.patch(`UserAccounts/syncAll`)
.then(() => this.vnApp.showSuccess(this.$t('LDAP users synchronized')));
}
onUserSync() {
if (!this.syncUser)
throw new UserError('Please enter the username');
let params = {password: this.syncPassword};
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
.then(() => this.vnApp.showSuccess(this.$t('User synchronized')));
}
onSyncClose() {
this.syncUser = '';
this.syncPassword = '';
}
}
ngModule.component('vnAccountLdap', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,16 @@
Host: Host
RDN: RDN
Base DN: DN base
Password should be base64 encoded: La contraseña debe estar codificada en base64
Filter: Filtro
Group DN: DN grupos
Synchronize now: Sincronizar ahora
Synchronize user: Sincronizar usuario
If password is not specified, just user attributes are synchronized: >-
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
Synchronizing in the background: Sincronizando en segundo plano
LDAP users synchronized: Usuarios LDAP sincronizados
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized: Usuario sincronizado

View File

@ -0,0 +1,69 @@
<vn-watcher
vn-id="watcher"
url="AccountConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="Homedir"
ng-model="$ctrl.config.homedir"
rule="AccountConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="Shell"
ng-model="$ctrl.config.shell"
rule="AccountConfig">
</vn-textfield>
<vn-input-number
label="Id base"
ng-model="$ctrl.config.idBase"
rule="AccountConfig">
</vn-input-number>
<vn-horizontal>
<vn-input-number
label="Min"
ng-model="$ctrl.config.min"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Max"
ng-model="$ctrl.config.max"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
<vn-horizontal>
<vn-input-number
label="Warn"
ng-model="$ctrl.config.warn"
rule="AccountConfig">
</vn-input-number>
<vn-input-number
label="Inact"
ng-model="$ctrl.config.inact"
rule="AccountConfig">
</vn-input-number>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnAccountPosix', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,16 @@
Host: Host
RDN: RDN
Base DN: DN base
Password should be base64 encoded: La contraseña debe estar codificada en base64
Filter: Filtro
Group DN: DN grupos
Synchronize now: Sincronizar ahora
Synchronize user: Sincronizar usuario
If password is not specified, just user attributes are synchronized: >-
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
Synchronizing in the background: Sincronizando en segundo plano
LDAP users synchronized: Usuarios LDAP sincronizados
Username: Nombre de usuario
Synchronize: Sincronizar
Please enter the username: Por favor introduce el nombre de usuario
User synchronized: Usuario sincronizado

View File

@ -9,6 +9,9 @@
{"state": "account.index", "icon": "face"},
{"state": "account.role", "icon": "group"},
{"state": "account.alias", "icon": "email"},
{"state": "account.posix", "icon": "accessibility"},
{"state": "account.ldap", "icon": "account_tree"},
{"state": "account.samba", "icon": "desktop_windows"},
{"state": "account.acl", "icon": "check"},
{"state": "account.connections", "icon": "share"}
],
@ -174,6 +177,24 @@
"state": "account.alias.card.users",
"component": "vn-alias-users",
"description": "Users"
}, {
"url": "/posix",
"state": "account.posix",
"component": "vn-account-posix",
"description": "Posix",
"acl": ["developer"]
}, {
"url": "/ldap",
"state": "account.ldap",
"component": "vn-account-ldap",
"description": "LDAP",
"acl": ["developer"]
}, {
"url": "/samba",
"state": "account.samba",
"component": "vn-account-samba",
"description": "Samba",
"acl": ["developer"]
}, {
"url": "/acl?q",
"state": "account.acl",

View File

@ -0,0 +1,47 @@
<vn-watcher
vn-id="watcher"
url="SambaConfigs"
data="$ctrl.config"
id-value="1"
form="form">
</vn-watcher>
<form
name="form"
ng-submit="watcher.submit()"
class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-textfield
label="SSH host"
ng-model="$ctrl.config.host"
rule="SambaConfig"
vn-focus>
</vn-textfield>
<vn-textfield
label="User"
ng-model="$ctrl.config.sshUser"
rule="SambaConfig">
</vn-textfield>
<vn-textfield
label="Password"
ng-model="$ctrl.config.sshPass"
info="Password should be base64 encoded"
type="password"
rule="SambaConfig">
</vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-button
label="Undo changes"
ng-if="watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
<vn-submit
icon="save"
vn-tooltip="Save"
class="round"
fixed-bottom-right>
</vn-submit>
</form>

View File

@ -0,0 +1,9 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {}
ngModule.component('vnAccountSamba', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,2 @@
SSH host: Host SSH
Password should be base64 encoded: La contraseña debe estar codificada en base64

27448
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -17,7 +17,7 @@
"helmet": "^3.21.2",
"i18n": "^0.8.4",
"imap": "^0.8.19",
"ldapjs": "^1.0.2",
"ldapjs": "^2.2.0",
"loopback": "^3.26.0",
"loopback-boot": "^2.27.1",
"loopback-component-explorer": "^6.5.0",

1743
print/package-lock.json generated

File diff suppressed because it is too large Load Diff