fix: refs #6432 account sync fixes
This commit is contained in:
parent
9c6b594426
commit
4f5242e3ae
10
Dockerfile
10
Dockerfile
|
@ -17,15 +17,19 @@ RUN apt-get update \
|
||||||
|
|
||||||
# Puppeteer
|
# Puppeteer
|
||||||
|
|
||||||
RUN apt-get update \
|
RUN apt-get install -y --no-install-recommends \
|
||||||
&& apt-get install -y --no-install-recommends \
|
|
||||||
libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
|
libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
|
||||||
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
|
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
|
||||||
libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \
|
libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \
|
||||||
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
|
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
|
||||||
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
|
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
|
||||||
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
|
||||||
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget \
|
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
|
||||||
|
|
||||||
|
# Extra dependencies
|
||||||
|
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
samba-common-bin \
|
||||||
&& rm -rf /var/lib/apt/lists/* \
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
&& npm -g install pm2
|
&& npm -g install pm2
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
ALTER TABLE account.sambaConfig
|
||||||
|
ADD userDn varchar(255) NOT NULL COMMENT 'Base DN for users without domain DN part';
|
|
@ -1,3 +1,4 @@
|
||||||
|
const ForbiddenError = require('vn-loopback/util/forbiddenError');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('sync', {
|
Self.remoteMethod('sync', {
|
||||||
|
@ -32,9 +33,13 @@ module.exports = Self => {
|
||||||
|
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const user = await models.VnUser.findOne({
|
const user = await models.VnUser.findOne({
|
||||||
fields: ['id'],
|
fields: ['id', 'password'],
|
||||||
where: {name: userName}
|
where: {name: userName}
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
|
if (user && password && !await user.hasPassword(password))
|
||||||
|
throw new ForbiddenError('Wrong password');
|
||||||
|
|
||||||
const isSync = !await models.UserSync.exists(userName, myOptions);
|
const isSync = !await models.UserSync.exists(userName, myOptions);
|
||||||
|
|
||||||
if (!force && isSync && user) return;
|
if (!force && isSync && user) return;
|
||||||
|
@ -42,4 +47,3 @@ module.exports = Self => {
|
||||||
await models.UserSync.destroyById(userName, myOptions);
|
await models.UserSync.destroyById(userName, myOptions);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ const crypto = require('crypto');
|
||||||
const nthash = require('smbhash').nthash;
|
const nthash = require('smbhash').nthash;
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
const shouldSync = process.env.NODE_ENV === 'production';
|
const shouldSync = process.env.NODE_ENV !== 'test';
|
||||||
|
|
||||||
Self.getSynchronizer = async function() {
|
Self.getSynchronizer = async function() {
|
||||||
return await Self.findOne({
|
return await Self.findOne({
|
||||||
|
@ -140,6 +140,7 @@ module.exports = Self => {
|
||||||
try {
|
try {
|
||||||
if (shouldSync)
|
if (shouldSync)
|
||||||
await client.del(dn);
|
await client.del(dn);
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
console.log(` -> User '${userName}' removed from LDAP`);
|
console.log(` -> User '${userName}' removed from LDAP`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e.name !== 'NoSuchObjectError') throw e;
|
if (e.name !== 'NoSuchObjectError') throw e;
|
||||||
|
|
|
@ -27,8 +27,7 @@ module.exports = Self => {
|
||||||
const [row] = await Self.rawSql(
|
const [row] = await Self.rawSql(
|
||||||
`SELECT COUNT(*) AS nRows
|
`SELECT COUNT(*) AS nRows
|
||||||
FROM mysql.user
|
FROM mysql.user
|
||||||
WHERE User = ?
|
WHERE User = ? AND Host = ?`,
|
||||||
AND Host = ?`,
|
|
||||||
[mysqlUser, this.userHost]
|
[mysqlUser, this.userHost]
|
||||||
);
|
);
|
||||||
let userExists = row.nRows > 0;
|
let userExists = row.nRows > 0;
|
||||||
|
@ -38,8 +37,7 @@ module.exports = Self => {
|
||||||
const [row] = await Self.rawSql(
|
const [row] = await Self.rawSql(
|
||||||
`SELECT Priv AS priv
|
`SELECT Priv AS priv
|
||||||
FROM mysql.global_priv
|
FROM mysql.global_priv
|
||||||
WHERE User = ?
|
WHERE User = ? AND Host = ?`,
|
||||||
AND Host = ?`,
|
|
||||||
[mysqlUser, this.userHost]
|
[mysqlUser, this.userHost]
|
||||||
);
|
);
|
||||||
const priv = row && JSON.parse(row.priv);
|
const priv = row && JSON.parse(row.priv);
|
||||||
|
@ -88,10 +86,18 @@ module.exports = Self => {
|
||||||
else
|
else
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
await Self.rawSql('GRANT ? TO ?@?',
|
|
||||||
[role, mysqlUser, this.userHost]);
|
|
||||||
|
|
||||||
if (role) {
|
const [row] = await Self.rawSql(
|
||||||
|
`SELECT COUNT(*) AS nRows
|
||||||
|
FROM mysql.user
|
||||||
|
WHERE User = ? AND Host = ''`,
|
||||||
|
[role]
|
||||||
|
);
|
||||||
|
const roleExists = row.nRows > 0;
|
||||||
|
|
||||||
|
if (roleExists) {
|
||||||
|
await Self.rawSql('GRANT ? TO ?@?',
|
||||||
|
[role, mysqlUser, this.userHost]);
|
||||||
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
|
await Self.rawSql('SET DEFAULT ROLE ? FOR ?@?',
|
||||||
[role, mysqlUser, this.userHost]);
|
[role, mysqlUser, this.userHost]);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
|
|
||||||
const ldap = require('../util/ldapjs-extra');
|
const ldap = require('../util/ldapjs-extra');
|
||||||
const ssh = require('node-ssh');
|
const execFile = require('child_process').execFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Summary of userAccountControl flags:
|
* Summary of userAccountControl flags:
|
||||||
|
@ -11,6 +11,8 @@ const UserAccountControlFlags = {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
const shouldSync = process.env.NODE_ENV !== 'test';
|
||||||
|
|
||||||
Self.getSynchronizer = async function() {
|
Self.getSynchronizer = async function() {
|
||||||
return await Self.findOne({
|
return await Self.findOne({
|
||||||
fields: [
|
fields: [
|
||||||
|
@ -19,6 +21,7 @@ module.exports = Self => {
|
||||||
'adController',
|
'adController',
|
||||||
'adUser',
|
'adUser',
|
||||||
'adPassword',
|
'adPassword',
|
||||||
|
'userDn',
|
||||||
'verifyCert'
|
'verifyCert'
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
@ -26,88 +29,119 @@ module.exports = Self => {
|
||||||
|
|
||||||
Object.assign(Self.prototype, {
|
Object.assign(Self.prototype, {
|
||||||
async init() {
|
async init() {
|
||||||
let sshClient = new ssh.NodeSSH();
|
const baseDn = this.adDomain
|
||||||
await sshClient.connect({
|
.split('.')
|
||||||
host: this.adController,
|
.map(part => `dc=${part}`)
|
||||||
username: this.adUser,
|
.join(',');
|
||||||
password: this.adPassword
|
const ldapUser = `cn=${this.adUser},cn=Users,${baseDn}`;
|
||||||
});
|
|
||||||
|
|
||||||
let adUser = `cn=${this.adUser},${this.usersDn()}`;
|
const adClient = ldap.createClient({
|
||||||
|
|
||||||
let adClient = ldap.createClient({
|
|
||||||
url: `ldaps://${this.adController}:636`,
|
url: `ldaps://${this.adController}:636`,
|
||||||
tlsOptions: {rejectUnauthorized: this.verifyCert}
|
tlsOptions: {rejectUnauthorized: this.verifyCert}
|
||||||
});
|
});
|
||||||
await adClient.bind(adUser, this.adPassword);
|
await adClient.bind(ldapUser, this.adPassword);
|
||||||
|
|
||||||
Object.assign(this, {
|
Object.assign(this, {
|
||||||
sshClient,
|
adClient,
|
||||||
adClient
|
fullUsersDn: `${this.userDn},${baseDn}`
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async deinit() {
|
async deinit() {
|
||||||
await this.sshClient.dispose();
|
|
||||||
await this.adClient.unbind();
|
await this.adClient.unbind();
|
||||||
},
|
},
|
||||||
|
|
||||||
usersDn() {
|
async sambaTool(command, args = []) {
|
||||||
let dnBase = this.adDomain
|
const authArgs = [
|
||||||
.split('.')
|
'--URL', `ldap://${this.adController}`,
|
||||||
.map(part => `dc=${part}`)
|
'--username', this.adUser,
|
||||||
.join(',');
|
'--password', this.adPassword
|
||||||
return `cn=Users,${dnBase}`;
|
];
|
||||||
|
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 syncUser(userName, info, password) {
|
async getAdUser(userName) {
|
||||||
let {sshClient} = this;
|
const sambaUser = await this.adClient.searchOne(this.fullUsersDn, {
|
||||||
|
|
||||||
let sambaUser = await this.adClient.searchOne(this.usersDn(), {
|
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['userAccountControl'],
|
attributes: [
|
||||||
|
'dn',
|
||||||
|
'userAccountControl',
|
||||||
|
'uidNumber',
|
||||||
|
'accountExpires',
|
||||||
|
'mail'
|
||||||
|
],
|
||||||
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
|
filter: `(&(objectClass=user)(sAMAccountName=${userName}))`
|
||||||
});
|
});
|
||||||
let isEnabled = sambaUser
|
if (sambaUser) {
|
||||||
&& !(sambaUser.userAccountControl & UserAccountControlFlags.ACCOUNTDISABLE);
|
for (const intProp of ['uidNumber', 'userAccountControl']) {
|
||||||
|
if (sambaUser[intProp] != null)
|
||||||
if (process.env.NODE_ENV === 'test')
|
sambaUser[intProp] = parseInt(sambaUser[intProp]);
|
||||||
return;
|
}
|
||||||
|
}
|
||||||
|
return sambaUser;
|
||||||
|
},
|
||||||
|
|
||||||
|
async syncUser(userName, info, password) {
|
||||||
|
let sambaUser = await this.getAdUser(userName);
|
||||||
|
let entry;
|
||||||
|
|
||||||
if (info.hasAccount) {
|
if (info.hasAccount) {
|
||||||
if (!sambaUser) {
|
if (!sambaUser) {
|
||||||
await sshClient.exec('samba-tool user create', [
|
await this.sambaTool('user', [
|
||||||
userName,
|
'create', userName,
|
||||||
'--uid-number', `${info.uidNumber}`,
|
'--userou', this.userDn,
|
||||||
'--mail-address', info.corporateMail,
|
|
||||||
'--random-password'
|
'--random-password'
|
||||||
]);
|
]);
|
||||||
await sshClient.exec('samba-tool user setexpiry', [
|
sambaUser = await this.getAdUser(userName);
|
||||||
userName,
|
|
||||||
'--noexpiry'
|
|
||||||
]);
|
|
||||||
await sshClient.exec('mkhomedir_helper', [
|
|
||||||
userName,
|
|
||||||
'0027'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
if (!isEnabled) {
|
|
||||||
await sshClient.exec('samba-tool user enable', [
|
|
||||||
userName
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
if (password) {
|
if (password) {
|
||||||
await sshClient.exec('samba-tool user setpassword', [
|
await this.sambaTool('user', [
|
||||||
userName,
|
'setpassword', userName,
|
||||||
'--newpassword', password
|
'--newpassword', password
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
} else if (isEnabled) {
|
|
||||||
await sshClient.exec('samba-tool user disable', [
|
entry = {
|
||||||
userName
|
userAccountControl: sambaUser.userAccountControl
|
||||||
]);
|
& ~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
|
||||||
console.log(` -> User '${userName}' disabled on Samba`);
|
console.log(` -> User '${userName}' disabled on Samba`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
if (changes.length)
|
||||||
|
await this.adClient.modify(sambaUser.dn, changes);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -117,14 +151,15 @@ module.exports = Self => {
|
||||||
*/
|
*/
|
||||||
async getUsers(usersToSync) {
|
async getUsers(usersToSync) {
|
||||||
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
|
const LDAP_MATCHING_RULE_BIT_AND = '1.2.840.113556.1.4.803';
|
||||||
let filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
|
// eslint-disable-next-line max-len
|
||||||
|
const filter = `!(userAccountControl:${LDAP_MATCHING_RULE_BIT_AND}:=${UserAccountControlFlags.ACCOUNTDISABLE})`;
|
||||||
|
|
||||||
let opts = {
|
const opts = {
|
||||||
scope: 'sub',
|
scope: 'sub',
|
||||||
attributes: ['sAMAccountName'],
|
attributes: ['sAMAccountName'],
|
||||||
filter: `(&(objectClass=user)(${filter}))`
|
filter: `(&(objectClass=user)(${filter}))`
|
||||||
};
|
};
|
||||||
await this.adClient.searchForeach(this.usersDn(), opts,
|
await this.adClient.searchForeach(this.fullUsersDn, opts,
|
||||||
o => usersToSync.add(o.sAMAccountName));
|
o => usersToSync.add(o.sAMAccountName));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,6 +28,10 @@
|
||||||
"adPassword": {
|
"adPassword": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"userDn": {
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
"verifyCert": {
|
"verifyCert": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,40 +12,40 @@
|
||||||
<vn-card class="vn-pa-lg" vn-focus>
|
<vn-card class="vn-pa-lg" vn-focus>
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Homedir base"
|
label="Homedir base"
|
||||||
ng-model="$ctrl.config.homedir"
|
ng-model="$ctrl.config.homedir"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
label="Shell"
|
label="Shell"
|
||||||
ng-model="$ctrl.config.shell"
|
ng-model="$ctrl.config.shell"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-textfield>
|
</vn-textfield>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="User and role base id"
|
label="User and role base id"
|
||||||
ng-model="$ctrl.config.idBase"
|
ng-model="$ctrl.config.idBase"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="Min"
|
label="Min"
|
||||||
ng-model="$ctrl.config.min"
|
ng-model="$ctrl.config.min"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="Max"
|
label="Max"
|
||||||
ng-model="$ctrl.config.max"
|
ng-model="$ctrl.config.max"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="Warn"
|
label="Warn"
|
||||||
ng-model="$ctrl.config.warn"
|
ng-model="$ctrl.config.warn"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
<vn-input-number
|
<vn-input-number
|
||||||
label="Inact"
|
label="Inact"
|
||||||
ng-model="$ctrl.config.inact"
|
ng-model="$ctrl.config.inact"
|
||||||
rule="AccountConfig">
|
rule="AccountConfig">
|
||||||
</vn-input-number>
|
</vn-input-number>
|
||||||
|
@ -61,10 +61,6 @@
|
||||||
label="Synchronize all"
|
label="Synchronize all"
|
||||||
ng-click="$ctrl.onSynchronizeAll()">
|
ng-click="$ctrl.onSynchronizeAll()">
|
||||||
</vn-button>
|
</vn-button>
|
||||||
<vn-button
|
|
||||||
label="Synchronize user"
|
|
||||||
ng-click="syncUser.show()">
|
|
||||||
</vn-button>
|
|
||||||
<vn-button
|
<vn-button
|
||||||
label="Synchronize roles"
|
label="Synchronize roles"
|
||||||
ng-click="$ctrl.onSynchronizeRoles()">
|
ng-click="$ctrl.onSynchronizeRoles()">
|
||||||
|
@ -77,25 +73,3 @@
|
||||||
</vn-button>
|
</vn-button>
|
||||||
</vn-button-bar>
|
</vn-button-bar>
|
||||||
</form>
|
</form>
|
||||||
<vn-dialog
|
|
||||||
vn-id="syncUser"
|
|
||||||
on-accept="$ctrl.onUserSync()"
|
|
||||||
on-close="$ctrl.onSyncClose()">
|
|
||||||
<tpl-body>
|
|
||||||
<vn-textfield
|
|
||||||
label="Username"
|
|
||||||
ng-model="$ctrl.syncUser"
|
|
||||||
vn-focus>
|
|
||||||
</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>
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
import UserError from 'core/lib/user-error';
|
|
||||||
|
|
||||||
export default class Controller extends Section {
|
export default class Controller extends Section {
|
||||||
onSynchronizeAll() {
|
onSynchronizeAll() {
|
||||||
|
@ -8,27 +7,10 @@ export default class Controller extends Section {
|
||||||
this.$http.patch(`Accounts/syncAll`);
|
this.$http.patch(`Accounts/syncAll`);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUserSync() {
|
|
||||||
if (!this.syncUser)
|
|
||||||
throw new UserError('Please enter the username');
|
|
||||||
|
|
||||||
let params = {
|
|
||||||
password: this.syncPassword,
|
|
||||||
force: true
|
|
||||||
};
|
|
||||||
return this.$http.patch(`Accounts/${this.syncUser}/sync`, params)
|
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
|
|
||||||
}
|
|
||||||
|
|
||||||
onSynchronizeRoles() {
|
onSynchronizeRoles() {
|
||||||
this.$http.patch(`RoleInherits/sync`)
|
this.$http.patch(`RoleInherits/sync`)
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!')));
|
.then(() => this.vnApp.showSuccess(this.$t('Roles synchronized!')));
|
||||||
}
|
}
|
||||||
|
|
||||||
onSyncClose() {
|
|
||||||
this.syncUser = '';
|
|
||||||
this.syncPassword = '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.component('vnAccountAccounts', {
|
ngModule.component('vnAccountAccounts', {
|
||||||
|
|
|
@ -3,7 +3,6 @@ Homedir base: Directorio base para carpetas de usuario
|
||||||
Shell: Intérprete de línea de comandos
|
Shell: Intérprete de línea de comandos
|
||||||
User and role base id: Id base usuarios y roles
|
User and role base id: Id base usuarios y roles
|
||||||
Synchronize all: Sincronizar todo
|
Synchronize all: Sincronizar todo
|
||||||
Synchronize user: Sincronizar usuario
|
|
||||||
Synchronize roles: Sincronizar roles
|
Synchronize roles: Sincronizar roles
|
||||||
If password is not specified, just user attributes are synchronized: >-
|
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
|
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||||
|
@ -12,5 +11,4 @@ Users synchronized!: ¡Usuarios sincronizados!
|
||||||
Username: Nombre de usuario
|
Username: Nombre de usuario
|
||||||
Synchronize: Sincronizar
|
Synchronize: Sincronizar
|
||||||
Please enter the username: Por favor introduce el nombre de usuario
|
Please enter the username: Por favor introduce el nombre de usuario
|
||||||
User synchronized!: ¡Usuario sincronizado!
|
|
||||||
Roles synchronized!: ¡Roles sincronizados!
|
Roles synchronized!: ¡Roles sincronizados!
|
||||||
|
|
|
@ -67,6 +67,15 @@
|
||||||
translate>
|
translate>
|
||||||
Deactivate user
|
Deactivate user
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
<vn-item
|
||||||
|
ng-if="$ctrl.user.active"
|
||||||
|
ng-click="syncUser.show()"
|
||||||
|
name="synchronizeUser"
|
||||||
|
vn-acl="it"
|
||||||
|
vn-acl-action="remove"
|
||||||
|
translate>
|
||||||
|
Synchronize
|
||||||
|
</vn-item>
|
||||||
</slot-menu>
|
</slot-menu>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -153,6 +162,32 @@
|
||||||
<button response="accept" translate>Change password</button>
|
<button response="accept" translate>Change password</button>
|
||||||
</tpl-buttons>
|
</tpl-buttons>
|
||||||
</vn-dialog>
|
</vn-dialog>
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="syncUser"
|
||||||
|
on-accept="$ctrl.onSync()"
|
||||||
|
on-close="$ctrl.onSyncClose()">
|
||||||
|
<tpl-title ng-translate>
|
||||||
|
Do you want to synchronize user?
|
||||||
|
</tpl-title>
|
||||||
|
<tpl-body>
|
||||||
|
<vn-check
|
||||||
|
label="Synchronize password"
|
||||||
|
ng-model="$ctrl.shouldSyncPassword"
|
||||||
|
info="If password is not specified, just user attributes are synchronized"
|
||||||
|
vn-focus>
|
||||||
|
</vn-check>
|
||||||
|
<vn-textfield
|
||||||
|
label="Password"
|
||||||
|
ng-model="$ctrl.syncPassword"
|
||||||
|
type="password"
|
||||||
|
ng-if="$ctrl.shouldSyncPassword">
|
||||||
|
</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>
|
||||||
<vn-popup vn-id="summary">
|
<vn-popup vn-id="summary">
|
||||||
<vn-user-summary user="$ctrl.user"></vn-user-summary>
|
<vn-user-summary user="$ctrl.user"></vn-user-summary>
|
||||||
</vn-popup>
|
</vn-popup>
|
||||||
|
|
|
@ -120,6 +120,20 @@ class Controller extends Descriptor {
|
||||||
this.vnApp.showSuccess(this.$t(message));
|
this.vnApp.showSuccess(this.$t(message));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSync() {
|
||||||
|
let params = {
|
||||||
|
password: this.syncPassword,
|
||||||
|
force: true
|
||||||
|
};
|
||||||
|
return this.$http.patch(`Accounts/${this.user.name}/sync`, params)
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('User synchronized!')));
|
||||||
|
}
|
||||||
|
|
||||||
|
onSyncClose() {
|
||||||
|
this.shouldSyncPassword = false;
|
||||||
|
this.syncPassword = undefined;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.component('vnUserDescriptor', {
|
ngModule.component('vnUserDescriptor', {
|
||||||
|
|
|
@ -22,6 +22,10 @@ Old password: Contraseña antigua
|
||||||
New password: Nueva contraseña
|
New password: Nueva contraseña
|
||||||
Repeat password: Repetir contraseña
|
Repeat password: Repetir contraseña
|
||||||
Password changed succesfully!: ¡Contraseña modificada correctamente!
|
Password changed succesfully!: ¡Contraseña modificada correctamente!
|
||||||
|
Synchronize: Sincronizar
|
||||||
|
Do you want to synchronize user?: ¿Quieres sincronizar el usuario?
|
||||||
|
Synchronize password: Sincronizar contraseña
|
||||||
|
User synchronized!: ¡Usuario sincronizado!
|
||||||
Role changed succesfully!: ¡Rol modificado correctamente!
|
Role changed succesfully!: ¡Rol modificado correctamente!
|
||||||
Password requirements: >
|
Password requirements: >
|
||||||
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
La contraseña debe tener al menos {{ length }} caracteres de longitud,
|
||||||
|
|
Loading…
Reference in New Issue