mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
UI: Update Auth Method Popup (#25366)
* Glimmerize auth-method model, move check for aws into template * Replace access/methods popup menu * Add popup menu coverage * remove unused imports * remove buttonText arg
This commit is contained in:
@@ -15,6 +15,7 @@ export default class VaultClusterAccessMethodsController extends Controller {
|
||||
@tracked authMethodOptions = [];
|
||||
@tracked selectedAuthType = null;
|
||||
@tracked selectedAuthName = null;
|
||||
@tracked methodToDisable = null;
|
||||
|
||||
queryParams = ['page, pageFilter'];
|
||||
|
||||
@@ -80,6 +81,8 @@ export default class VaultClusterAccessMethodsController extends Controller {
|
||||
this.flashMessages.danger(
|
||||
`There was an error disabling Auth Method at ${path}: ${err.errors.join(' ')}.`
|
||||
);
|
||||
} finally {
|
||||
this.methodToDisable = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,13 @@
|
||||
*/
|
||||
|
||||
import Model, { belongsTo, hasMany, attr } from '@ember-data/model';
|
||||
import { alias } from '@ember/object/computed'; // eslint-disable-line
|
||||
import { computed } from '@ember/object'; // eslint-disable-line
|
||||
import { service } from '@ember/service';
|
||||
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
|
||||
import apiPath from 'vault/utils/api-path';
|
||||
import attachCapabilities from 'vault/lib/attach-capabilities';
|
||||
import { withModelValidations } from 'vault/decorators/model-validations';
|
||||
import { allMethods } from 'vault/helpers/mountable-auth-methods';
|
||||
import lazyCapabilities from 'vault/macros/lazy-capabilities';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
const validations = {
|
||||
path: [
|
||||
@@ -25,51 +24,51 @@ const validations = {
|
||||
],
|
||||
};
|
||||
|
||||
// unsure if ember-api-actions will work on native JS class model
|
||||
// for now create class to use validations and then use classic extend pattern
|
||||
@withModelValidations(validations)
|
||||
class AuthMethodModel extends Model {}
|
||||
const ModelExport = AuthMethodModel.extend({
|
||||
store: service(),
|
||||
export default class AuthMethodModel extends Model {
|
||||
@service store;
|
||||
|
||||
config: belongsTo('mount-config', { async: false, inverse: null }), // one-to-none that replaces former fragment
|
||||
authConfigs: hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }),
|
||||
path: attr('string'),
|
||||
accessor: attr('string'),
|
||||
name: attr('string'),
|
||||
type: attr('string'),
|
||||
@belongsTo('mount-config', { async: false, inverse: null }) config; // one-to-none that replaces former fragment
|
||||
@hasMany('auth-config', { polymorphic: true, inverse: 'backend', async: false }) authConfigs;
|
||||
@attr('string') path;
|
||||
@attr('string') accessor;
|
||||
@attr('string') name;
|
||||
@attr('string') type;
|
||||
// namespaces introduced types with a `ns_` prefix for built-in engines
|
||||
// so we need to strip that to normalize the type
|
||||
methodType: computed('type', function () {
|
||||
get methodType() {
|
||||
return this.type.replace(/^ns_/, '');
|
||||
}),
|
||||
icon: computed('methodType', function () {
|
||||
}
|
||||
get icon() {
|
||||
const authMethods = allMethods().find((backend) => backend.type === this.methodType);
|
||||
|
||||
return authMethods?.glyph || 'users';
|
||||
}),
|
||||
description: attr('string', {
|
||||
}
|
||||
@attr('string', {
|
||||
editType: 'textarea',
|
||||
}),
|
||||
local: attr('boolean', {
|
||||
})
|
||||
description;
|
||||
@attr('boolean', {
|
||||
helpText:
|
||||
'When Replication is enabled, a local mount will not be replicated across clusters. This can only be specified at mount time.',
|
||||
}),
|
||||
sealWrap: attr('boolean', {
|
||||
})
|
||||
local;
|
||||
@attr('boolean', {
|
||||
helpText:
|
||||
'When enabled - if a seal supporting seal wrapping is specified in the configuration, all critical security parameters (CSPs) in this backend will be seal wrapped. (For KV mounts, all values will be seal wrapped.) This can only be specified at mount time.',
|
||||
}),
|
||||
})
|
||||
sealWrap;
|
||||
|
||||
// used when the `auth` prefix is important,
|
||||
// currently only when setting perf mount filtering
|
||||
apiPath: computed('path', function () {
|
||||
get apiPath() {
|
||||
return `auth/${this.path}`;
|
||||
}),
|
||||
localDisplay: computed('local', function () {
|
||||
}
|
||||
get localDisplay() {
|
||||
return this.local ? 'local' : 'replicated';
|
||||
}),
|
||||
}
|
||||
|
||||
tuneAttrs: computed('path', function () {
|
||||
get tuneAttrs() {
|
||||
const { methodType } = this;
|
||||
let tuneAttrs;
|
||||
// token_type should not be tuneable for the token auth method
|
||||
@@ -85,9 +84,9 @@ const ModelExport = AuthMethodModel.extend({
|
||||
];
|
||||
}
|
||||
return expandAttributeMeta(this, tuneAttrs);
|
||||
}),
|
||||
}
|
||||
|
||||
formFields: computed(function () {
|
||||
get formFields() {
|
||||
return [
|
||||
'type',
|
||||
'path',
|
||||
@@ -97,9 +96,9 @@ const ModelExport = AuthMethodModel.extend({
|
||||
'sealWrap',
|
||||
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
|
||||
];
|
||||
}),
|
||||
}
|
||||
|
||||
formFieldGroups: computed(function () {
|
||||
get formFieldGroups() {
|
||||
return [
|
||||
{ default: ['path'] },
|
||||
{
|
||||
@@ -112,30 +111,30 @@ const ModelExport = AuthMethodModel.extend({
|
||||
],
|
||||
},
|
||||
];
|
||||
}),
|
||||
}
|
||||
|
||||
attrs: computed('formFields', function () {
|
||||
get attrs() {
|
||||
return expandAttributeMeta(this, this.formFields);
|
||||
}),
|
||||
}
|
||||
|
||||
fieldGroups: computed('formFieldGroups', function () {
|
||||
get fieldGroups() {
|
||||
return fieldToAttrs(this, this.formFieldGroups);
|
||||
}),
|
||||
canDisable: alias('deletePath.canDelete'),
|
||||
canEdit: alias('configPath.canUpdate'),
|
||||
}
|
||||
@lazyCapabilities(apiPath`sys/auth/${'id'}`, 'id') deletePath;
|
||||
@lazyCapabilities(apiPath`auth/${'id'}/config`, 'id') configPath;
|
||||
@lazyCapabilities(apiPath`auth/${'id'}/config/client`, 'id') awsConfigPath;
|
||||
get canDisable() {
|
||||
return this.deletePath.get('canDelete') !== false;
|
||||
}
|
||||
get canEdit() {
|
||||
return this.configPath.get('canUpdate') !== false;
|
||||
}
|
||||
get canEditAws() {
|
||||
return this.awsConfigPath.get('canUpdate') !== false;
|
||||
}
|
||||
|
||||
@action
|
||||
tune(data) {
|
||||
return this.store.adapterFor('auth-method').tune(this.path, data);
|
||||
},
|
||||
});
|
||||
|
||||
export default attachCapabilities(ModelExport, {
|
||||
deletePath: apiPath`sys/auth/${'id'}`,
|
||||
configPath: function (context) {
|
||||
if (context.type === 'aws') {
|
||||
return apiPath`auth/${'id'}/config/client`.call(this, context);
|
||||
} else {
|
||||
return apiPath`auth/${'id'}/config`.call(this, context);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,36 +69,41 @@
|
||||
</div>
|
||||
<div class="level-right is-flex is-paddingless is-marginless">
|
||||
<div class="level-item">
|
||||
<PopupMenu @name="auth-backend-nav">
|
||||
<nav class="menu" aria-label="navigation for managing access method {{method.id}}">
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.access.method.section" @models={{array method.id "configuration"}}>
|
||||
View configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{#if method.canEdit}}
|
||||
<li>
|
||||
<LinkTo @route="vault.cluster.settings.auth.configure" @model={{method.id}}>
|
||||
Edit configuration
|
||||
</LinkTo>
|
||||
</li>
|
||||
{{/if}}
|
||||
|
||||
{{#if (and (not-eq method.methodType "token") method.canDisable)}}
|
||||
<ConfirmAction
|
||||
@isInDropdown={{true}}
|
||||
@confirmTitle="Disable method?"
|
||||
@confirmMessage="This may affect access to Vault data."
|
||||
@buttonText="Disable"
|
||||
@onConfirmAction={{perform this.disableMethod method}}
|
||||
/>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</nav>
|
||||
</PopupMenu>
|
||||
<Hds::Dropdown @isInline={{true}} @listPosition="bottom-right" as |dd|>
|
||||
<dd.ToggleIcon
|
||||
@icon="more-horizontal"
|
||||
@text="Overflow options"
|
||||
@hasChevron={{false}}
|
||||
data-test-popup-menu-trigger
|
||||
/>
|
||||
<dd.Interactive
|
||||
@text="View configuration"
|
||||
@route="vault.cluster.access.method.section"
|
||||
@models={{array method.id "configuration"}}
|
||||
/>
|
||||
{{#if (or method.canEdit (and (eq method.methodType "aws") method.canEditAws))}}
|
||||
<dd.Interactive
|
||||
@text="Edit configuration"
|
||||
@route="vault.cluster.settings.auth.configure"
|
||||
@model={{method.id}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and (not-eq method.methodType "token") method.canDisable)}}
|
||||
<dd.Interactive @text="Disable" @color="critical" {{on "click" (fn (mut this.methodToDisable) method)}} />
|
||||
{{/if}}
|
||||
</Hds::Dropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LinkedBlock>
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
|
||||
{{#if this.methodToDisable}}
|
||||
<ConfirmModal
|
||||
@color="critical"
|
||||
@confirmTitle="Disable method?"
|
||||
@confirmMessage="This may affect access to Vault data."
|
||||
@onClose={{fn (mut this.methodToDisable) null}}
|
||||
@onConfirm={{perform this.disableMethod this.methodToDisable}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -75,7 +75,7 @@ module('Acceptance | auth backend list', function (hooks) {
|
||||
});
|
||||
|
||||
test('auth methods are linkable and link to correct view', async function (assert) {
|
||||
assert.expect(16);
|
||||
assert.expect(24);
|
||||
const uid = uuidv4();
|
||||
await visit('/vault/access');
|
||||
|
||||
@@ -83,15 +83,22 @@ module('Acceptance | auth backend list', function (hooks) {
|
||||
const backends = supportedAuthBackends();
|
||||
for (const backend of backends) {
|
||||
const { type } = backend;
|
||||
const path = `auth-list-${type}-${uid}`;
|
||||
const path = type === 'token' ? 'token' : `auth-list-${type}-${uid}`;
|
||||
if (type !== 'token') {
|
||||
await enablePage.enable(type, path);
|
||||
}
|
||||
await settled();
|
||||
await visit('/vault/access');
|
||||
|
||||
// check popup menu
|
||||
const itemCount = type === 'token' ? 2 : 3;
|
||||
await click(`[data-test-auth-backend-link="${path}"] [data-test-popup-menu-trigger]`);
|
||||
assert
|
||||
.dom('.hds-dropdown-list-item')
|
||||
.exists({ count: itemCount }, `shows ${itemCount} dropdown items for ${type}`);
|
||||
|
||||
// all auth methods should be linkable
|
||||
await click(`[data-test-auth-backend-link="${type === 'token' ? type : path}"]`);
|
||||
await click(`[data-test-auth-backend-link="${path}"]`);
|
||||
if (!supportManaged.includes(type)) {
|
||||
assert.dom('[data-test-auth-section-tab]').exists({ count: 1 });
|
||||
assert
|
||||
@@ -106,6 +113,8 @@ module('Acceptance | auth backend list', function (hooks) {
|
||||
assert
|
||||
.dom('[data-test-auth-section-tab]')
|
||||
.exists({ count: expectedTabs }, `has management tabs for ${type} auth method`);
|
||||
}
|
||||
if (type !== 'token') {
|
||||
// cleanup method
|
||||
await runCmd(deleteAuthCmd(path));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user