From 92223b600e5c84c8fa43e0049b6162428ac0b2c4 Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Wed, 29 Sep 2021 14:35:00 -0600 Subject: [PATCH] KV search box when no list access to metadata (#12626) * get credentials card test and setup * call getcrednetials card and remove path test error * configuration * metadata search box * changelog * checking if it is noReadAccess * try removing test * blah * a test * blah * stuff * attempting a clean up to solve issue * Another attempt * test1 * test2 * test3 * test4 * test5 * test6 * test7 * finally? * clean up --- changelog/12626.txt | 3 + ui/app/components/get-credentials-card.js | 30 +- ui/app/components/secret-create-or-update.js | 4 +- ui/app/components/secret-edit.js | 3 +- .../cluster/secrets/backend/configuration.js | 28 +- .../vault/cluster/secrets/backend/list.js | 6 + .../vault/cluster/secrets/backend/metadata.js | 18 +- .../components/get-credentials-card.hbs | 5 +- ui/app/templates/components/input-search.hbs | 1 + .../components/secret-create-or-update.hbs | 6 +- ui/app/templates/components/secret-edit.hbs | 5 +- .../vault/cluster/secrets/backend/list.hbs | 205 ++++++------ .../cluster/secrets/backend/metadata.hbs | 45 ++- .../cluster/secrets/backend/overview.hbs | 1 + .../templates/components/search-select.hbs | 1 + .../secrets/backend/kv/secret-test.js | 301 ++++++++++-------- .../settings/mount-secret-backend-test.js | 45 --- .../components/get-credentials-card-test.js | 18 +- .../pages/secrets/backend/kv/edit-secret.js | 8 + 19 files changed, 410 insertions(+), 323 deletions(-) create mode 100644 changelog/12626.txt diff --git a/changelog/12626.txt b/changelog/12626.txt new file mode 100644 index 000000000..7c3cc7cb2 --- /dev/null +++ b/changelog/12626.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Add KV secret search box when no metadata list access. +``` \ No newline at end of file diff --git a/ui/app/components/get-credentials-card.js b/ui/app/components/get-credentials-card.js index 9bc650aac..a989205bc 100644 --- a/ui/app/components/get-credentials-card.js +++ b/ui/app/components/get-credentials-card.js @@ -5,11 +5,16 @@ * * @example * ```js - * + * * ``` * @param title=null {String} - The title displays the card title * @param searchLabel=null {String} - The text above the searchSelect component * @param models=null {Array} - An array of model types to fetch from the API. Passed through to SearchSelect component + * @param type=null {String} - Determines where the transitionTo goes. If role to backend.credentials, if secret backend.show + * @param shouldUseFallback=[false] {Boolean} - If true the input is used instead of search select. + * @param subText=[null] {String} - Text below title + * @param placeHolder=[null] {String} - Only works if shouldUseFallback is true. Displays the helper text inside the input. + * @param backend=null {String} - Name of the backend to look up on search. */ import Component from '@glimmer/component'; @@ -20,25 +25,36 @@ export default class GetCredentialsCard extends Component { @service router; @service store; @tracked role = ''; + @tracked secret = ''; @action async transitionToCredential() { const role = this.role; + const secret = this.secret; if (role) { this.router.transitionTo('vault.cluster.secrets.backend.credentials', role); } + if (secret) { + this.router.transitionTo('vault.cluster.secrets.backend.show', secret); + } } get buttonDisabled() { - return !this.role; + return !this.role && !this.secret; } @action handleRoleInput(value) { - // if it comes in from the fallback component then the value is a string otherwise it's an array - let role = value; - if (Array.isArray(value)) { - role = value[0]; + if (this.args.type === 'role') { + // if it comes in from the fallback component then the value is a string otherwise it's an array + // which currently only happens if type is role. + if (Array.isArray(value)) { + this.role = value[0]; + } else { + this.role = value; + } + } + if (this.args.type === 'secret') { + this.secret = value; } - this.role = role; } } diff --git a/ui/app/components/secret-create-or-update.js b/ui/app/components/secret-create-or-update.js index f58e9605e..1ecf0a868 100644 --- a/ui/app/components/secret-create-or-update.js +++ b/ui/app/components/secret-create-or-update.js @@ -11,7 +11,7 @@ * @modelForData={{@modelForData}} * @isV2=true * @secretData={{@secretData}} - * @canCreateSecretMetadata=true + * @canCreateSecretMetadata=false * /> * ``` * @param {string} mode - create, edit, show determines what view to display @@ -20,7 +20,7 @@ * @param {object} modelForData - a class that helps track secret data, defined in secret-edit * @param {boolean} isV2 - whether or not KV1 or KV2 * @param {object} secretData - class that is created in secret-edit - * @param {boolean} canCreateSecretMetadata - based on permissions to the /metadata/ endpoint. If user has secret create access. + * @param {boolean} canUpdateSecretMetadata - based on permissions to the /metadata/ endpoint. If user has secret update. create is not enough for metadata. */ import Component from '@glimmer/component'; diff --git a/ui/app/components/secret-edit.js b/ui/app/components/secret-edit.js index f49ac0e77..cb375f1de 100644 --- a/ui/app/components/secret-edit.js +++ b/ui/app/components/secret-edit.js @@ -109,7 +109,8 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, { 'mode' ), canDeleteSecretMetadata: alias('checkMetadataCapabilities.canDelete'), - canCreateSecretMetadata: alias('checkMetadataCapabilities.canCreate'), + canUpdateSecretMetadata: alias('checkMetadataCapabilities.canUpdate'), + canReadSecretMetadata: alias('checkMetadataCapabilities.canRead'), requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'), diff --git a/ui/app/routes/vault/cluster/secrets/backend/configuration.js b/ui/app/routes/vault/cluster/secrets/backend/configuration.js index 46a8482ca..2d7bcf32b 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/configuration.js +++ b/ui/app/routes/vault/cluster/secrets/backend/configuration.js @@ -4,19 +4,31 @@ import Route from '@ember/routing/route'; export default Route.extend({ wizard: service(), store: service(), - model() { + async model() { let backend = this.modelFor('vault.cluster.secrets.backend'); if (this.wizard.featureState === 'list') { this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', backend.get('type')); } if (backend.isV2KV) { - // design wants specific default to show that can't be set in the model - backend.set('casRequired', backend.casRequired ? backend.casRequired : 'False'); - backend.set( - 'deleteVersionAfter', - backend.deleteVersionAfter !== '0s' ? backend.deleteVersionAfter : 'Never delete' - ); - backend.set('maxVersions', backend.maxVersions ? backend.maxVersions : 'Not set'); + let canRead = await this.store + .findRecord('capabilities', `${backend.id}/config`) + .then(response => response.canRead); + // only set these config params if they can read the config endpoint. + if (canRead) { + // design wants specific default to show that can't be set in the model + backend.set('casRequired', backend.casRequired ? backend.casRequired : 'False'); + backend.set( + 'deleteVersionAfter', + backend.deleteVersionAfter !== '0s' ? backend.deleteVersionAfter : 'Never delete' + ); + backend.set('maxVersions', backend.maxVersions ? backend.maxVersions : 'Not set'); + } else { + // remove the default values from the model if they don't have read access otherwise it will display the defaults even if they've been set (because they error on returning config data) + // normally would catch the config error in the secret-v2 adapter, but I need the functions to proceed, not stop. So we remove the values here. + backend.set('casRequired', null); + backend.set('deleteVersionAfter', null); + backend.set('maxVersions', null); + } } return backend; }, diff --git a/ui/app/routes/vault/cluster/secrets/backend/list.js b/ui/app/routes/vault/cluster/secrets/backend/list.js index f24244496..4c657142d 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/list.js +++ b/ui/app/routes/vault/cluster/secrets/backend/list.js @@ -10,6 +10,7 @@ const SUPPORTED_BACKENDS = supportedSecretBackends(); export default Route.extend({ templateName: 'vault/cluster/secrets/backend/list', pathHelp: service('path-help'), + noMetadataPermissions: false, queryParams: { page: { refreshModel: true, @@ -111,6 +112,9 @@ export default Route.extend({ // if we're at the root we don't want to throw if (backendModel && err.httpStatus === 404 && secret === '') { return []; + } else if (backendModel.engineType === 'kv' && backendModel.isV2KV) { + this.set('noMetadataPermissions', true); + return []; } else { // else we're throwing and dealing with this in the error action throw err; @@ -149,6 +153,7 @@ export default Route.extend({ let backend = this.enginePathParam(); let backendModel = this.store.peekRecord('secret-engine', backend); let has404 = this.has404; + let noMetadataPermissions = this.noMetadataPermissions; // only clear store cache if this is a new model if (secret !== controller.get('baseKey.id')) { this.store.clearAllDatasets(); @@ -157,6 +162,7 @@ export default Route.extend({ controller.setProperties({ model, has404, + noMetadataPermissions, backend, backendModel, baseKey: { id: secret }, diff --git a/ui/app/routes/vault/cluster/secrets/backend/metadata.js b/ui/app/routes/vault/cluster/secrets/backend/metadata.js index b47b0ddc9..bcfd261d9 100644 --- a/ui/app/routes/vault/cluster/secrets/backend/metadata.js +++ b/ui/app/routes/vault/cluster/secrets/backend/metadata.js @@ -3,6 +3,7 @@ import { inject as service } from '@ember/service'; export default class MetadataShow extends Route { @service store; + noReadAccess = false; beforeModel() { const { backend } = this.paramsFor('vault.cluster.secrets.backend'); @@ -11,14 +12,23 @@ export default class MetadataShow extends Route { model(params) { let { secret } = params; - return this.store.queryRecord('secret-v2', { - backend: this.backend, - id: secret, - }); + return this.store + .queryRecord('secret-v2', { + backend: this.backend, + id: secret, + }) + .catch(error => { + // there was an error likely in read metadata. + // still load the page and handle what you show by filtering for this property + if (error.httpStatus === 403) { + this.noReadAccess = true; + } + }); } setupController(controller, model) { controller.set('backend', this.backend); // for backendCrumb controller.set('model', model); + controller.set('noReadAccess', this.noReadAccess); } } diff --git a/ui/app/templates/components/get-credentials-card.hbs b/ui/app/templates/components/get-credentials-card.hbs index 8e2d06d8c..5a539af5a 100644 --- a/ui/app/templates/components/get-credentials-card.hbs +++ b/ui/app/templates/components/get-credentials-card.hbs @@ -1,18 +1,21 @@ -
+

{{@title}}

{{@searchLabel}}

+

{{@subText}}

diff --git a/ui/app/templates/components/secret-create-or-update.hbs b/ui/app/templates/components/secret-create-or-update.hbs index 7a5cae09c..0efbe0da3 100644 --- a/ui/app/templates/components/secret-create-or-update.hbs +++ b/ui/app/templates/components/secret-create-or-update.hbs @@ -97,7 +97,8 @@ {{/each}} {{/if}} - {{#if (and @isV2 @canCreateSecretMetadata) }} + {{!-- must have UPDATE permissions to add secret metadata. Create only will not work --}} + {{#if (and @isV2 @canUpdateSecretMetadata)}} {{/if}} - {{/if}} + {{/if}}