mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 09:42:25 +00:00
Mask obfuscated Secret sync create/edit fields (#27348)
* wip not working on edit view * changelog * vercel and fix tests * need conditional to not break all the things: * create test coverage and add for other obfustcaed fonts, still missing one. * Update 27348.txt * remove meep * comment * test coverage
This commit is contained in:
3
changelog/27348.txt
Normal file
3
changelog/27348.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
```release-note:improvement
|
||||
ui: Mask obfuscated fields when creating/editing a Secrets sync destination.
|
||||
```
|
||||
@@ -32,6 +32,8 @@ export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinat
|
||||
label: 'Access key ID',
|
||||
subText:
|
||||
'Access key ID to authenticate against the secrets manager. If empty, Vault will use the AWS_ACCESS_KEY_ID environment variable if configured.',
|
||||
sensitive: true,
|
||||
noCopy: true,
|
||||
})
|
||||
accessKeyId; // obfuscated, never returned by API
|
||||
|
||||
@@ -39,6 +41,8 @@ export default class SyncDestinationsAwsSecretsManagerModel extends SyncDestinat
|
||||
label: 'Secret access key',
|
||||
subText:
|
||||
'Secret access key to authenticate against the secrets manager. If empty, Vault will use the AWS_SECRET_ACCESS_KEY environment variable if configured.',
|
||||
sensitive: true,
|
||||
noCopy: true,
|
||||
})
|
||||
secretAccessKey; // obfuscated, never returned by API
|
||||
|
||||
|
||||
@@ -55,6 +55,8 @@ export default class SyncDestinationsAzureKeyVaultModel extends SyncDestinationM
|
||||
@attr('string', {
|
||||
subText:
|
||||
'Client secret of an Azure app registration. If empty, Vault will use the AZURE_CLIENT_SECRET environment variable if configured.',
|
||||
sensitive: true,
|
||||
noCopy: true,
|
||||
})
|
||||
clientSecret; // obfuscated, never returned by API
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class SyncDestinationsGoogleCloudSecretManagerModel extends SyncD
|
||||
editType: 'file',
|
||||
docLink: '/vault/docs/secrets/gcp#authentication',
|
||||
})
|
||||
credentials; // obfuscated, never returned by API
|
||||
credentials; // obfuscated, never returned by API. Masking handled by EnableInput component
|
||||
|
||||
@attr('object', {
|
||||
subText:
|
||||
|
||||
@@ -27,6 +27,8 @@ export default class SyncDestinationsGithubModel extends SyncDestinationModel {
|
||||
@attr('string', {
|
||||
subText:
|
||||
'Personal access token to authenticate to the GitHub repository. If empty, Vault will use the GITHUB_ACCESS_TOKEN environment variable if configured.',
|
||||
sensitive: true,
|
||||
noCopy: true,
|
||||
})
|
||||
accessToken; // obfuscated, never returned by API
|
||||
|
||||
|
||||
@@ -42,6 +42,8 @@ const formFieldGroups = [
|
||||
export default class SyncDestinationsVercelProjectModel extends SyncDestinationModel {
|
||||
@attr('string', {
|
||||
subText: 'Vercel API access token with the permissions to manage environment variables.',
|
||||
sensitive: true,
|
||||
noCopy: true,
|
||||
})
|
||||
accessToken; // obfuscated, never returned by API
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@
|
||||
<MaskedInput
|
||||
@name={{@attr.name}}
|
||||
@value={{or (get @model this.valuePath) @attr.options.defaultValue}}
|
||||
@allowCopy="true"
|
||||
@allowCopy={{if @attr.options.noCopy false true}}
|
||||
@onChange={{this.setAndBroadcast}}
|
||||
@onKeyUp={{@onKeyUp}}
|
||||
/>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
aria-label={{or @name "masked input"}}
|
||||
{{on "change" this.onChange}}
|
||||
{{on "keyup" (fn this.handleKeyUp @name)}}
|
||||
data-test-textarea
|
||||
data-test-textarea={{or @name ""}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if @allowCopy}}
|
||||
|
||||
@@ -86,7 +86,7 @@ module('Acceptance | sync | destination (singular)', function (hooks) {
|
||||
|
||||
await visit('vault/sync/secrets/destinations/vercel-project/destination-vercel/edit');
|
||||
await click(ts.enableField('accessToken'));
|
||||
await fillIn(ts.inputByAttr('accessToken'), 'foobar');
|
||||
await fillIn(ts.maskedInput('accessToken'), 'foobar');
|
||||
await click(ts.saveButton);
|
||||
await click(ts.toolbar('Edit destination'));
|
||||
await click(ts.saveButton);
|
||||
|
||||
@@ -79,4 +79,5 @@ export const GENERAL = {
|
||||
navLink: (label: string) => `[data-test-sidebar-nav-link="${label}"]`,
|
||||
cancelButton: '[data-test-cancel]',
|
||||
saveButton: '[data-test-save]',
|
||||
maskedInput: (name: string) => `[data-test-textarea="${name}"]`,
|
||||
};
|
||||
|
||||
@@ -97,6 +97,11 @@ export const PAGE = {
|
||||
case 'customTags':
|
||||
await fillIn('[data-test-kv-key="0"]', 'foo');
|
||||
return fillIn('[data-test-kv-value="0"]', value);
|
||||
case 'accessKeyId':
|
||||
case 'secretAccessKey':
|
||||
case 'clientSecret':
|
||||
case 'accessToken':
|
||||
return fillIn(GENERAL.maskedInput(attr), value);
|
||||
case 'deploymentEnvironments':
|
||||
await click('[data-test-input="deploymentEnvironments"] input#development');
|
||||
await click('[data-test-input="deploymentEnvironments"] input#preview');
|
||||
|
||||
@@ -10,7 +10,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import sinon from 'sinon';
|
||||
import { Response } from 'miragejs';
|
||||
import { click, render, typeIn } from '@ember/test-helpers';
|
||||
import { click, fillIn, render, typeIn } from '@ember/test-helpers';
|
||||
import { PAGE } from 'vault/tests/helpers/sync/sync-selectors';
|
||||
import { syncDestinations } from 'vault/helpers/sync-destinations';
|
||||
import { decamelize, underscore } from '@ember/string';
|
||||
@@ -131,6 +131,28 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
await click(PAGE.saveButton);
|
||||
});
|
||||
|
||||
test('edit: payload only contains masked inputs when they have changed', async function (assert) {
|
||||
assert.expect(1);
|
||||
this.model = this.generateModel();
|
||||
|
||||
this.server.patch(`sys/sync/destinations/${this.model.type}/${this.model.name}`, (schema, req) => {
|
||||
const payload = JSON.parse(req.requestBody);
|
||||
assert.propEqual(
|
||||
payload,
|
||||
{ secret_access_key: 'new-secret' },
|
||||
'payload contains the changed obfuscated field'
|
||||
);
|
||||
return { payload };
|
||||
});
|
||||
|
||||
await this.renderFormComponent();
|
||||
await click(PAGE.enableField('accessKeyId'));
|
||||
await click(PAGE.maskedInput('accessKeyId')); // click on input but do not change value
|
||||
await click(PAGE.enableField('secretAccessKey'));
|
||||
await fillIn(PAGE.maskedInput('secretAccessKey'), 'new-secret');
|
||||
await click(PAGE.saveButton);
|
||||
});
|
||||
|
||||
test('it renders API errors', async function (assert) {
|
||||
assert.expect(1);
|
||||
const name = 'my-failed-dest';
|
||||
@@ -192,6 +214,7 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
// CREATE FORM ASSERTIONS FOR EACH DESTINATION TYPE
|
||||
for (const destination of SYNC_DESTINATIONS) {
|
||||
const { name, type } = destination;
|
||||
const obfuscatedFields = ['accessToken', 'clientSecret', 'secretAccessKey', 'accessKeyId'];
|
||||
|
||||
module(`create destination: ${type}`, function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
@@ -209,6 +232,23 @@ module('Integration | Component | sync | Secrets::Page::Destinations::CreateAndE
|
||||
}
|
||||
});
|
||||
|
||||
test('it masks obfuscated fields', async function (assert) {
|
||||
const filteredObfuscatedFields = this.model.formFields.filter((field) =>
|
||||
obfuscatedFields.includes(field.name)
|
||||
);
|
||||
assert.expect(filteredObfuscatedFields.length);
|
||||
await this.renderFormComponent();
|
||||
// iterate over the form fields and filter for those that are obfuscated
|
||||
// fill those in and assert that they are masked
|
||||
filteredObfuscatedFields.forEach(async (field) => {
|
||||
await fillIn(PAGE.maskedInput(field.name), 'blah');
|
||||
|
||||
assert
|
||||
.dom(PAGE.maskedInput(field.name))
|
||||
.hasClass('masked-font', `it renders ${field.name} for ${destination} with masked font`);
|
||||
});
|
||||
});
|
||||
|
||||
test('it saves destination and transitions to details', async function (assert) {
|
||||
assert.expect(5);
|
||||
const name = 'my-name';
|
||||
|
||||
Reference in New Issue
Block a user