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

UI: KV version 2 advanced secret updates #25235

Merged
merged 9 commits into from
Feb 6, 2024
Merged
3 changes: 3 additions & 0 deletions changelog/25235.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:bug
ui: Do not disable JSON display toggle for KV version 2 secrets
```
2 changes: 1 addition & 1 deletion ui/lib/core/addon/components/json-editor.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
data-test-restore-example
/>
{{/if}}
{{#if (and @obscure @readOnly)}}
{{#if (and @allowObscure @readOnly)}}
{{! For safety we only use obscured values in readonly mode }}
<div>
<Toggle @name="revealValues" @checked={{this.revealValues}} @onChange={{fn (mut this.revealValues)}}>
Expand Down
2 changes: 1 addition & 1 deletion ui/lib/core/addon/components/json-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class JsonEditorComponent extends Component {
}

get showObfuscatedData() {
return this.args.readOnly && this.args.obscure && !this.revealValues;
return this.args.readOnly && this.args.allowObscure && !this.revealValues;
}
get obfuscatedData() {
return stringify([obfuscateData(JSON.parse(this.args.value))], {});
Expand Down
12 changes: 10 additions & 2 deletions ui/lib/core/addon/components/kv-object-editor.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
@value={{row.name}}
placeholder={{this.placeholders.key}}
{{on "change" (fn this.updateRow row index)}}
{{on "input" (fn this.validateKey index)}}
class="input"
/>
</div>
Expand Down Expand Up @@ -71,7 +70,7 @@
</div>
{{/unless}}
</div>
{{#if (includes index this.whitespaceWarningRows)}}
{{#if (this.showWhitespaceWarning row.name)}}
<div class="has-bottom-margin-s">
<AlertInline
@type="warning"
Expand All @@ -80,6 +79,15 @@
/>
</div>
{{/if}}
{{#if (this.showNonStringWarning row.value)}}
<div class="has-bottom-margin-s">
<AlertInline
@type="warning"
@message="This value will be saved as a string. If you need to save a non-string value, please use the JSON editor."
data-test-kv-object-warning={{index}}
/>
</div>
{{/if}}
{{/each}}
{{#if this.hasDuplicateKeys}}
<Hds::Alert data-test-duplicate-keys-warning @type="inline" @color="warning" as |A|>
Expand Down
33 changes: 15 additions & 18 deletions ui/lib/core/addon/components/kv-object-editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { isNone } from '@ember/utils';
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import { guidFor } from '@ember/object/internals';
import { A } from '@ember/array';
import KVObject from 'vault/lib/kv-object';

/**
Expand All @@ -26,7 +25,7 @@ import KVObject from 'vault/lib/kv-object';
* ```
* @param {string} value - the value is captured from the model.
* @param {function} onChange - function that captures the value on change
* @param {boolean} [isMasked = false] - when true the <MaskedInput> renders instead of the default <textarea> to input the value portion of the key/value object
* @param {boolean} [isMasked = false] - when true the <MaskedInput> renders instead of the default <textarea> to input the value portion of the key/value object
* @param {boolean} [isSingleRow = false] - when true the kv object editor will only show one row and hide the Add button
* @param {function} [onKeyUp] - function passed in that handles the dom keyup event. Used for validation on the kv custom metadata.
* @param {string} [label] - label displayed over key value inputs
Expand All @@ -36,11 +35,12 @@ import KVObject from 'vault/lib/kv-object';
* @param {string} [subText] - placed under label.
* @param {string} [keyPlaceholder] - placeholder for key input
* @param {string} [valuePlaceholder] - placeholder for value input
* @param {boolean} [allowWhiteSpace = false] - when true, allows whitespace in the key input
* @param {boolean} [warnNonStringValues = false] - when true, shows a warning if the value is a non-string
*/

export default class KvObjectEditor extends Component {
@tracked kvData;
whitespaceWarningRows = A();

get placeholders() {
return {
Expand Down Expand Up @@ -79,7 +79,6 @@ export default class KvObjectEditor extends Component {
const oldObj = this.kvData.objectAt(index);
assert('object guids match', guidFor(oldObj) === guidFor(object));
this.kvData.removeAt(index);
this.whitespaceWarningRows.removeObject(index);
this.args.onChange(this.kvData.toJSON());
}
@action
Expand All @@ -88,19 +87,17 @@ export default class KvObjectEditor extends Component {
this.args.onKeyUp(event.target.value);
}
}
@action
validateKey(rowIndex, event) {
if (this.args.allowWhiteSpace) {
return;
showWhitespaceWarning = (name) => {
if (this.args.allowWhiteSpace) return false;
return new RegExp('\\s', 'g').test(name);
};
showNonStringWarning = (value) => {
if (!this.args.warnNonStringValues) return false;
try {
JSON.parse(value);
return true;
} catch (e) {
return false;
}
const { value } = event.target;
const keyHasWhitespace = new RegExp('\\s', 'g').test(value);
const rows = [...this.whitespaceWarningRows];
const rowHasWarning = rows.includes(rowIndex);
if (!keyHasWhitespace && rowHasWarning) {
this.whitespaceWarningRows.removeObject(rowIndex);
} else if (keyHasWhitespace && !rowHasWarning) {
this.whitespaceWarningRows.addObject(rowIndex);
}
}
};
}
3 changes: 2 additions & 1 deletion ui/lib/kv/addon/components/kv-data-fields.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<JsonEditor
@title="{{if (eq @type 'create') 'Secret' 'Version'}} data"
@value={{this.stringifiedSecretData}}
@obscure={{@obscureJson}}
@allowObscure={{true}}
@valueUpdated={{this.handleJson}}
@readOnly={{eq @type "details"}}
/>
Expand Down Expand Up @@ -46,5 +46,6 @@
@value={{@secret.secretData}}
@onChange={{fn (mut @secret.secretData)}}
@isMasked={{true}}
@warnNonStringValues={{true}}
/>
{{/if}}
1 change: 0 additions & 1 deletion ui/lib/kv/addon/components/kv-data-fields.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import { stringify } from 'core/helpers/stringify';
* @param {object} [modelValidations] - object of errors. If attr.name is in object and has error message display in AlertInline.
* @param {callback} [pathValidations] - callback function fired for the path input on key up
* @param {boolean} [type=null] - can be edit, create, or details. Used to change text for some form labels
* @param {boolean} [obscureJson=false] - used to obfuscate json values in JsonEditor
*/

export default class KvDataFields extends Component {
Expand Down
10 changes: 2 additions & 8 deletions ui/lib/kv/addon/components/page/secret/details.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,7 @@

<:toolbarFilters>
{{#unless this.emptyState}}
<Toggle
@name="json"
@checked={{or this.showJsonView this.secretDataIsAdvanced}}
@onChange={{fn (mut this.showJsonView)}}
@disabled={{this.secretDataIsAdvanced}}
>
<Toggle @name="json" @checked={{this.showJsonView}} @onChange={{fn (mut this.showJsonView)}}>
<span class="has-text-grey">JSON</span>
</Toggle>
{{/unless}}
Expand Down Expand Up @@ -143,8 +138,7 @@
</EmptyState>
{{else}}
<KvDataFields
@showJson={{or this.showJsonView this.secretDataIsAdvanced}}
@obscureJson={{this.secretDataIsAdvanced}}
@showJson={{this.showJsonView}}
@secret={{@secret}}
@modelValidations={{this.modelValidations}}
@type="details"
Expand Down
6 changes: 4 additions & 2 deletions ui/lib/kv/addon/components/page/secret/details.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,15 @@ export default class KvSecretDetails extends Component {
@tracked showJsonView = false;
@tracked wrappedData = null;
@tracked syncStatus = null; // array of association sync status info by destination
secretDataIsAdvanced;

constructor() {
super(...arguments);
this.fetchSyncStatus.perform();
this.originalSecret = JSON.stringify(this.args.secret.secretData || {});
this.secretDataIsAdvanced = isAdvancedSecret(this.originalSecret);
if (isAdvancedSecret(this.originalSecret)) {
// Default to JSON view if advanced
this.showJsonView = true;
}
}

@action
Expand Down
16 changes: 3 additions & 13 deletions ui/lib/kv/addon/components/page/secret/edit.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@

<KvPageHeader @breadcrumbs={{@breadcrumbs}} @pageTitle="Create New Version">
<:toolbarFilters>
<Toggle
@name="json"
@checked={{or this.showJsonView this.secretDataIsAdvanced}}
@onChange={{fn (mut this.showJsonView)}}
@disabled={{this.secretDataIsAdvanced}}
>
<Toggle @name="json" @checked={{this.showJsonView}} @onChange={{fn (mut this.showJsonView)}}>
<span class="has-text-grey">JSON</span>
</Toggle>
</:toolbarFilters>
Expand Down Expand Up @@ -43,19 +38,14 @@
<MessageError @model={{@secret}} @errorMessage={{this.errorMessage}} />

<KvDataFields
@showJson={{or this.showJsonView this.secretDataIsAdvanced}}
@showJson={{this.showJsonView}}
@secret={{@secret}}
@modelValidations={{this.modelValidations}}
@type="edit"
/>

<div class="has-top-margin-m">
<Toggle
@name="Show diff"
@onChange={{fn (mut this.showDiff)}}
@checked={{this.showDiff}}
@disabled={{not this.diffDelta}}
>
<Toggle @name="Show diff" @onChange={{fn (mut this.showDiff)}} @checked={{this.showDiff}}>
<span class="ttl-picker-label is-large">Show diff</span><br />
<div class="description has-text-grey" data-test-diff-description>{{if
this.diffDelta
Expand Down
6 changes: 4 additions & 2 deletions ui/lib/kv/addon/components/page/secret/edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,14 @@ export default class KvSecretEdit extends Component {
@tracked modelValidations;
@tracked invalidFormAlert;
originalSecret;
secretDataIsAdvanced;

constructor() {
super(...arguments);
this.originalSecret = JSON.stringify(this.args.secret.secretData || {});
this.secretDataIsAdvanced = isAdvancedSecret(this.originalSecret);
if (isAdvancedSecret(this.originalSecret)) {
// Default to JSON view if advanced
this.showJsonView = true;
}
}

get showOldVersionAlert() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) {
await click(FORM.saveBtn);

// Details view
assert.dom(FORM.toggleJson).isDisabled();
assert.dom(FORM.toggleJson).isNotDisabled();
assert.dom(FORM.toggleJson).isChecked();
assert.strictEqual(
codemirror().getValue(),
Expand All @@ -298,7 +298,7 @@ module('Acceptance | kv-v2 workflow | edge cases', function (hooks) {

// New version view
await click(PAGE.detail.createNewVersion);
assert.dom(FORM.toggleJson).isDisabled();
assert.dom(FORM.toggleJson).isNotDisabled();
assert.dom(FORM.toggleJson).isChecked();
assert.false(codemirror().getValue().includes('*'), 'Values are not obscured on edit view');
});
Expand Down
1 change: 1 addition & 0 deletions ui/tests/helpers/kv/kv-selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const FORM = {
inputByAttr: (attr) => `[data-test-input="${attr}"]`,
fieldByAttr: (attr) => `[data=test=field="${attr}"]`, // formfield
toggleJson: '[data-test-toggle-input="json"]',
toggleJsonValues: '[data-test-toggle-input="revealValues"]',
toggleMasked: '[data-test-button="toggle-masked"]',
toggleMetadata: '[data-test-metadata-toggle]',
jsonEditor: '[data-test-component="code-mirror-modifier"]',
Expand Down
2 changes: 1 addition & 1 deletion ui/tests/integration/components/json-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ module('Integration | Component | json-editor', function (hooks) {
this.set('readOnly', true);
await render(hbs`<JsonEditor
@value={{this.json_blob}}
@obscure={{true}}
@allowObscure={{true}}
@readOnly={{this.readOnly}}
@valueUpdated={{this.valueUpdated}}
@onFocusOut={{this.onFocusOut}}
Expand Down
13 changes: 13 additions & 0 deletions ui/tests/integration/components/kv-object-editor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,17 @@ module('Integration | Component | kv-object-editor', function (hooks) {
await click('[data-test-kv-delete-row="0"]');
assert.dom('[data-test-kv-whitespace-warning="0"]').doesNotExist();
});

test('it should display object warning for values when warnNonStringValues true', async function (assert) {
const objValue = `{
"a": "b"
}`;
await render(hbs`<KvObjectEditor @onChange={{this.spy}} @warnNonStringValues={{true}} />`);
await fillIn('[data-test-kv-value="0"]', objValue);
assert.dom('[data-test-kv-object-warning="0"]').exists();
await fillIn('[data-test-kv-value="0"]', 'test ');
assert.dom('[data-test-kv-object-warning="0"]').doesNotExist();
await fillIn('[data-test-kv-value="0"]', 7);
assert.dom('[data-test-kv-object-warning="0"]').exists();
});
});
6 changes: 5 additions & 1 deletion ui/tests/integration/components/kv/kv-data-fields-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ module('Integration | Component | kv-v2 | KvDataFields', function (hooks) {
});

test('it shows readonly json editor when viewing secret details of complex secret', async function (assert) {
assert.expect(3);
assert.expect(4);
this.secret.secretData = {
foo: {
bar: 'baz',
Expand All @@ -109,6 +109,10 @@ module('Integration | Component | kv-v2 | KvDataFields', function (hooks) {
});
assert.dom(PAGE.infoRowValue('foo')).doesNotExist('does not render rows of secret data');
assert.dom('[data-test-component="code-mirror-modifier"]').hasClass('readonly-codemirror');
assert
.dom('[data-test-component="code-mirror-modifier"]')
.includesText(`{ "foo": { "bar": "********" }}`);
await click(FORM.toggleJsonValues);
assert.dom('[data-test-component="code-mirror-modifier"]').includesText(`{ "foo": { "bar": "baz" }}`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,15 @@ module('Integration | Component | kv-v2 | Page::Secret::Details', function (hook
await click(FORM.toggleMasked);
assert.dom(PAGE.infoRowValue('foo')).hasText('bar', 'renders secret value');
await click(FORM.toggleJson);
await click(FORM.toggleJsonValues);
assert.propEqual(parseJsonEditor(find), this.secretData, 'json editor renders secret data');
assert
.dom(PAGE.detail.versionTimestamp)
.includesText(`Version ${this.version} created`, 'renders version and time created');
});

test('it renders json view when secret is complex', async function (assert) {
assert.expect(3);
assert.expect(4);
await render(
hbs`
<Page::Secret::Details
Expand All @@ -154,7 +155,8 @@ module('Integration | Component | kv-v2 | Page::Secret::Details', function (hook
{ owner: this.engine }
);
assert.dom(PAGE.infoRowValue('foo')).doesNotExist('does not render rows of secret data');
assert.dom(FORM.toggleJson).isDisabled();
assert.dom(FORM.toggleJson).isChecked();
assert.dom(FORM.toggleJson).isNotDisabled();
assert.dom('[data-test-component="code-mirror-modifier"]').exists('shows json editor');
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ module('Integration | Component | kv-v2 | Page::Secret::Edit', function (hooks)
{ owner: this.engine }
);

assert.dom(PAGE.edit.toggleDiff).isDisabled('Diff toggle is disabled');
assert.dom(PAGE.edit.toggleDiff).isNotDisabled('Diff toggle is not disabled');
assert.dom(PAGE.edit.toggleDiffDescription).hasText('No changes to show. Update secret to view diff');
assert.dom(PAGE.diff.visualDiff).doesNotExist('Does not show visual diff');

Expand Down