Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

KV search box when no list access to metadata #12626

Merged
merged 22 commits into from Sep 29, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions changelog/12626.txt
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Add KV secret search box when no metadata list access.
```
30 changes: 23 additions & 7 deletions ui/app/components/get-credentials-card.js
Expand Up @@ -5,11 +5,16 @@
*
* @example
* ```js
* <GetCredentialsCard @title="Get Credentials" @searchLabel="Role to use" @models={{array 'database/roles'}} />
* <GetCredentialsCard @title="Get Credentials" @searchLabel="Role to use" @models={{array 'database/roles'}} @type="role" @backend={{model.backend}}/>
* ```
* @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';
Expand All @@ -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;
}
}
3 changes: 2 additions & 1 deletion ui/app/components/secret-edit.js
Expand Up @@ -109,7 +109,8 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
'mode'
),
canDeleteSecretMetadata: alias('checkMetadataCapabilities.canDelete'),
canCreateSecretMetadata: alias('checkMetadataCapabilities.canCreate'),
canCreateSecretMetadata: or('checkMetadataCapabilities.canCreate', 'checkMetadataCapabilities.canUpdate'),
canReadSecretMetadata: alias('checkMetadataCapabilities.canRead'),

requestInFlight: or('model.isLoading', 'model.isReloading', 'model.isSaving'),

Expand Down
28 changes: 20 additions & 8 deletions ui/app/routes/vault/cluster/secrets/backend/configuration.js
Expand Up @@ -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;
},
Expand Down
6 changes: 6 additions & 0 deletions ui/app/routes/vault/cluster/secrets/backend/list.js
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -157,6 +162,7 @@ export default Route.extend({
controller.setProperties({
model,
has404,
noMetadataPermissions,
backend,
backendModel,
baseKey: { id: secret },
Expand Down
16 changes: 12 additions & 4 deletions ui/app/routes/vault/cluster/secrets/backend/metadata.js
Expand Up @@ -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');
Expand All @@ -11,14 +12,21 @@ 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(() => {
// there was an error likely in read metadata.
// still load the page and handle what you show by filtering for this property
this.noReadAccess = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to check for any particular error code?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question. Let me see if I can get more specific.

});
}

setupController(controller, model) {
controller.set('backend', this.backend); // for backendCrumb
controller.set('model', model);
controller.set('noReadAccess', this.noReadAccess);
}
}
5 changes: 4 additions & 1 deletion ui/app/templates/components/get-credentials-card.hbs
@@ -1,18 +1,21 @@
<form class="selectable-card is-rounded no-flex">
<form class="selectable-card is-rounded no-flex data-test-get-credentials-card">
<div class="is-flex-between is-fullwidth card-details" >
<h3 class="title is-5">{{@title}}</h3>
</div>
<div class="has-top-bottom-margin">
<p class="is-label search-label">{{@searchLabel}}</p>
</div>
<p class="sub-text">{{@subText}}</p>
<SearchSelect
@id={{id}}
@models={{@models}}
@selectLimit='1'
@backend={{@backend}}
@fallbackComponent='input-search'
@shouldUseFallback={{@shouldUseFallback}}
@onChange={{action 'handleRoleInput' }}
@inputValue={{get model valuePath}}
@placeHolder={{@placeHolder}}
data-test-search-roles
/>
<input
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/components/input-search.hbs
Expand Up @@ -5,6 +5,7 @@
@type="text"
@value={{this.searchInput}}
{{on 'keyup' this.inputChanged}}
@placeholder={{@placeHolder}}
/>
</div>
</div>
2 changes: 1 addition & 1 deletion ui/app/templates/components/secret-create-or-update.hbs
Expand Up @@ -113,7 +113,7 @@
@updateValidationErrorCount={{action "updateValidationErrorCount"}}
/>
{{/if}}
{{/if}}
{{/if}}
<div class="field is-grouped box is-fullwidth is-bottomless">
<div class="control">
<button
Expand Down
3 changes: 2 additions & 1 deletion ui/app/templates/components/secret-edit.hbs
Expand Up @@ -26,7 +26,8 @@
Secret
</LinkTo>
</LinkTo>
{{#if model.canReadMetadata}}
{{!-- must have read access to /metadata see tab or update to update metadata--}}
{{#if (or model.canReadMetadata this.canCreateSecretMetadata)}}
<LinkTo @route="vault.cluster.secrets.backend.metadata" @model={{key.id}} @tagName="li" @activeClass="is-active" data-test-secret-metadata-tab>
<LinkTo @route="vault.cluster.secrets.backend.metadata">
Metadata
Expand Down