Skip to content

Commit ccdd48d

Browse files
cherry pick (hashicorp#23264)
1 parent e345dd8 commit ccdd48d

File tree

7 files changed

+64
-27
lines changed

7 files changed

+64
-27
lines changed

changelog/23260.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
ui: Adds warning before downloading KV v2 secret values
3+
```

ui/lib/core/addon/components/download-button.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import { assert } from '@ember/debug';
3434
* @param {function} [fetchData] - function that fetches data and returns download content
3535
* @param {string} [extension='txt'] - file extension, the download service uses this to determine the mimetype
3636
* @param {boolean} [stringify=false] - argument to stringify the data before passing to the File constructor
37+
* @param {callback} [onSuccess] - callback from parent to invoke if download is successful
3738
*/
3839

3940
export default class DownloadButton extends Component {
@@ -73,6 +74,9 @@ export default class DownloadButton extends Component {
7374
try {
7475
this.download.miscExtension(this.filename, this.content, this.extension);
7576
this.flashMessages.info(`Downloading ${this.filename}`);
77+
if (this.args.onSuccess) {
78+
this.args.onSuccess();
79+
}
7680
} catch (error) {
7781
this.flashMessages.danger(errorMessage(error, 'There was a problem downloading. Please try again.'));
7882
}

ui/lib/core/addon/components/masked-input.hbs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -35,15 +35,9 @@
3535
</CopyButton>
3636
{{/if}}
3737
{{#if @allowDownload}}
38-
<DownloadButton
39-
class="button download-button"
40-
@filename={{or @name "secret-value"}}
41-
@data={{@value}}
42-
@stringify={{true}}
43-
aria-label="Download secret value"
44-
>
45-
<Icon @name="download" />
46-
</DownloadButton>
38+
<button type="button" class="button download-button" {{on "click" (fn (mut this.modalOpen) true)}}>
39+
<Icon data-test-download-icon @name="download" />
40+
</button>
4741
{{/if}}
4842
<button
4943
onclick={{this.toggleMask}}
@@ -55,4 +49,34 @@
5549
>
5650
<Icon @name={{if this.showValue "eye" "eye-off"}} />
5751
</button>
58-
</div>
52+
</div>
53+
54+
{{! CONFIRM DOWNLOAD MODAL }}
55+
{{#if @allowDownload}}
56+
<Modal
57+
@title="Download secret value?"
58+
@onClose={{action (mut this.modalOpen) false}}
59+
@isActive={{this.modalOpen}}
60+
@type="warning"
61+
>
62+
<section class="modal-card-body">
63+
This download is
64+
<strong>unencrypted</strong>. Are you sure you want to download this secret data as plaintext?
65+
</section>
66+
<footer class="modal-card-foot modal-card-foot-outlined">
67+
<DownloadButton
68+
class="button is-primary"
69+
@filename={{or @name "secret-value"}}
70+
@data={{@value}}
71+
@stringify={{true}}
72+
aria-label="Download secret value"
73+
@onSuccess={{fn (mut this.modalOpen) false}}
74+
>
75+
Download
76+
</DownloadButton>
77+
<button type="button" class="button is-secondary" {{on "click" (fn (mut this.modalOpen) false)}}>
78+
Cancel
79+
</button>
80+
</footer>
81+
</Modal>
82+
{{/if}}

ui/lib/core/addon/components/masked-input.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,14 @@ import autosize from 'autosize';
2727
* @param name {String} - The key correlated to the value. Used for the download file name.
2828
* @param [onChange=Callback] {Function|action} - Callback triggered on change, sends new value. Must set the value of @value
2929
* @param [allowCopy=false] {bool} - Whether or not the input should render with a copy button.
30+
* @param [allowDownload=false] {bool} - Renders a download button that prompts a confirmation modal to download the secret value
3031
* @param [displayOnly=false] {bool} - Whether or not to display the value as a display only `pre` element or as an input.
3132
*
3233
*/
3334
export default class MaskedInputComponent extends Component {
3435
textareaId = 'textarea-' + guidFor(this);
3536
@tracked showValue = false;
37+
@tracked modalOpen = false;
3638

3739
constructor() {
3840
super(...arguments);

ui/tests/index.html

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,27 @@
66

77
<html>
88
<head>
9-
<meta charset="utf-8">
9+
<meta charset="utf-8" />
1010
<title>Vault Tests</title>
11-
<meta name="description" content="">
12-
<meta name="viewport" content="width=device-width, initial-scale=1">
11+
<meta name="description" content="" />
12+
<meta name="viewport" content="width=device-width, initial-scale=1" />
1313

14-
{{content-for "head"}}
15-
{{content-for "test-head"}}
14+
{{content-for "head"}} {{content-for "test-head"}}
1615

17-
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css">
18-
<link rel="stylesheet" href="{{rootURL}}assets/vault.css">
19-
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css">
16+
<link rel="stylesheet" href="{{rootURL}}assets/vendor.css" />
17+
<link rel="stylesheet" href="{{rootURL}}assets/vault.css" />
18+
<link rel="stylesheet" href="{{rootURL}}assets/test-support.css" />
2019

21-
{{content-for "head-footer"}}
22-
{{content-for "test-head-footer"}}
20+
{{content-for "head-footer"}} {{content-for "test-head-footer"}}
2321
</head>
2422
<body>
25-
{{content-for "body"}}
26-
{{content-for "test-body"}}
23+
{{content-for "body"}} {{content-for "test-body"}}
2724

2825
<div id="qunit"></div>
2926
<div id="qunit-fixture">
3027
<div id="ember-testing-container">
3128
<div id="ember-testing"></div>
29+
<div id="modal-wormhole"></div>
3230
</div>
3331
</div>
3432

@@ -38,7 +36,6 @@
3836
<script src="{{rootURL}}assets/vault.js"></script>
3937
<script src="{{rootURL}}assets/tests.js"></script>
4038

41-
{{content-for "body-footer"}}
42-
{{content-for "test-body-footer"}}
39+
{{content-for "body-footer"}} {{content-for "test-body-footer"}}
4340
</body>
4441
</html>

ui/tests/integration/components/masked-input-test.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { module, test } from 'qunit';
77
import { setupRenderingTest } from 'ember-qunit';
8-
import { render, focus, triggerKeyEvent, typeIn, fillIn } from '@ember/test-helpers';
8+
import { render, focus, triggerKeyEvent, typeIn, fillIn, click } from '@ember/test-helpers';
99
import { create } from 'ember-cli-page-object';
1010
import hbs from 'htmlbars-inline-precompile';
1111
import sinon from 'sinon';
@@ -44,8 +44,14 @@ module('Integration | Component | masked input', function (hooks) {
4444
});
4545

4646
test('it renders a download button when allowDownload is true', async function (assert) {
47-
await render(hbs`<MaskedInput @allowDownload={{true}} />`);
48-
assert.ok(component.downloadButtonIsPresent);
47+
await render(hbs`<MaskedInput @allowDownload={{true}} /> <div id="modal-wormhole"></div>
48+
`);
49+
assert.ok(component.downloadIconIsPresent);
50+
51+
await click('[data-test-download-icon]');
52+
assert.ok(component.downloadButtonIsPresent, 'clicking download icon opens modal with download button');
53+
54+
assert;
4955
});
5056

5157
test('it shortens all outputs when displayOnly and masked', async function (assert) {

ui/tests/pages/components/masked-input.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { clickable, isPresent } from 'ember-cli-page-object';
88
export default {
99
textareaIsPresent: isPresent('[data-test-textarea]'),
1010
copyButtonIsPresent: isPresent('[data-test-copy-button]'),
11+
downloadIconIsPresent: isPresent('[data-test-download-icon]'),
1112
downloadButtonIsPresent: isPresent('[data-test-download-button]'),
1213
toggleMasked: clickable('[data-test-button="toggle-masked"]'),
1314
};

0 commit comments

Comments
 (0)