From e418017bc2a0bf97de2bc16ecff281df2eaba577 Mon Sep 17 00:00:00 2001 From: lisbet-alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Mon, 12 Dec 2022 16:37:01 -0800 Subject: [PATCH 01/25] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20create=20form=20fo?= =?UTF-8?q?r=20json=20credentials=20(#1498)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 create form for json credentials ✅ Closes: ICU-6682 * refactor: 💡 add new info field * refactor: 💡 add feature flag * refactor: 💡 add hds radio cards to json cred * Update ui/admin/tests/acceptance/credential-store/credentials/create-test.js * test: 💍 add test for json cred * chore: 🤖 Remove ember-data-model-fragments package * fix: 🐛 Update how json is added as a known credential type * refactor: 💡 update credentialTypes if statement to ternary * chore: 🤖 add ember-data back to addon/api project * chore: 🤖 remove ember-feature-flag from addon/api * refactor: 💡 remove if statement in cred form * refactor: 💡 update selectors for json cred. tests * refactor: 💡 Update Credential Form to use form.disabled * test: 💍 Fix test selectors for radio card credential tests * refactor: 💡 update credential types declaration to const * style: 💄 Fix style issue with SecretEditor in edit mode * refactor: 💡 remove disabled and readonly attributes in form * refactor: 💡 Create credential ability canRead override * test: 💍 add credential ability override tests * test: 💍 Fix brokered credential test with json support added * refactor: 💡 add attributes comment back to credential model Co-authored-by: Cameron Perera --- addons/api/addon/models/credential.js | 2 +- addons/core/translations/resources/en-us.yaml | 3 + ui/admin/app/abilities/credential.js | 13 ++ .../app/components/form/credential/index.hbs | 1 + .../app/components/form/credential/index.js | 19 ++ .../components/form/credential/json/index.hbs | 112 ++++++++++++ .../form/credential/ssh_private_key/index.hbs | 12 +- .../form/credential/ssh_private_key/index.js | 10 - .../credential/username_password/index.hbs | 10 +- .../credential/username_password/index.js | 10 - .../credential-store/credentials/new.js | 8 + ui/admin/app/styles/app.scss | 7 +- .../target/brokered-credential-sources.hbs | 2 +- .../credentials/create-test.js | 173 ++++++++++++++---- .../brokered-credential-sources-test.js | 28 ++- .../tests/unit/abilities/credential-test.js | 90 +++++++++ yarn.lock | 6 +- 17 files changed, 420 insertions(+), 86 deletions(-) create mode 100644 ui/admin/app/abilities/credential.js create mode 100644 ui/admin/app/components/form/credential/index.js create mode 100644 ui/admin/app/components/form/credential/json/index.hbs delete mode 100644 ui/admin/app/components/form/credential/ssh_private_key/index.js delete mode 100644 ui/admin/app/components/form/credential/username_password/index.js create mode 100644 ui/admin/tests/unit/abilities/credential-test.js diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index d7339b719..eb5474206 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -3,7 +3,7 @@ import GeneratedCredentialModel from '../generated/models/credential'; /** * Supported Credential types. */ -export const types = ['username_password', 'ssh_private_key']; +export const types = ['username_password', 'ssh_private_key', 'json']; export default class CredentialModel extends GeneratedCredentialModel { // =attributes diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 32f934c43..b2bfb2a95 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -455,6 +455,9 @@ credential: private_key_passphrase: label: Passphrase help: Optional. + json: + label: JSON + help: This is the JSON blob that provides credentials to the host. After this data is saved, you will not be able to edit it again without replacing it. scope: title: Scope title_plural: Scopes diff --git a/ui/admin/app/abilities/credential.js b/ui/admin/app/abilities/credential.js new file mode 100644 index 000000000..0faae0a97 --- /dev/null +++ b/ui/admin/app/abilities/credential.js @@ -0,0 +1,13 @@ +import CredentialAbility from 'api/abilities/credential'; +import { inject as service } from '@ember/service'; + +export default class OverrideCredentialAbility extends CredentialAbility { + // =service + @service features; + + get canRead() { + return (this.features.isEnabled('json-credentials') || this.model.type !== 'json') + ? super.canRead + : false; + } +} diff --git a/ui/admin/app/components/form/credential/index.hbs b/ui/admin/app/components/form/credential/index.hbs index a27830b5c..6d957e1b6 100644 --- a/ui/admin/app/components/form/credential/index.hbs +++ b/ui/admin/app/components/form/credential/index.hbs @@ -2,6 +2,7 @@ {{component (concat 'form/credential/' @model.type) model=@model + types=this.credentialTypes submit=@submit cancel=@cancel changeType=@changeType diff --git a/ui/admin/app/components/form/credential/index.js b/ui/admin/app/components/form/credential/index.js new file mode 100644 index 000000000..fa281a953 --- /dev/null +++ b/ui/admin/app/components/form/credential/index.js @@ -0,0 +1,19 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { types } from 'api/models/credential'; + +export default class FormCredentialComponent extends Component { + // =services + @service features; + + // =attributes + /** + * Returns an array of available credential types the user can create + * @type {object} + */ + get credentialTypes() { + return this.features.isEnabled('json-credentials') + ? types + : types.filter((type) => type !== 'json'); + } +} diff --git a/ui/admin/app/components/form/credential/json/index.hbs b/ui/admin/app/components/form/credential/json/index.hbs new file mode 100644 index 000000000..42f83c0a0 --- /dev/null +++ b/ui/admin/app/components/form/credential/json/index.hbs @@ -0,0 +1,112 @@ + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + <:body> + + {{t 'form.type.label'}} + {{#each @types as |credentialType|}} + + {{t + (concat 'resources.credential.types.' credentialType) + }} + {{t + (concat 'resources.credential.help.' credentialType) + }} + + {{/each}} + + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{else}} + + {{t 'form.type.label'}} + {{t (concat 'resources.credential.types.' @model.type)}} + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{/if}} + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs index c87c33efa..471893221 100644 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs +++ b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -46,7 +42,7 @@ {{#if @model.isNew}} {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{#if @model.errors.username}} @@ -102,8 +96,6 @@ @label={{t 'resources.credential.form.private_key.label'}} @helperText={{t 'resources.credential.form.private_key.help'}} @error={{@model.errors.private_key}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.private_key}} @@ -121,8 +113,6 @@ @label={{t 'resources.credential.form.private_key_passphrase.label'}} @helperText={{t 'resources.credential.form.private_key_passphrase.help'}} @error={{@model.errors.private_key_passphrase}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.js b/ui/admin/app/components/form/credential/ssh_private_key/index.js deleted file mode 100644 index c2c5d5bcb..000000000 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormSSHPrivateKeyCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/components/form/credential/username_password/index.hbs b/ui/admin/app/components/form/credential/username_password/index.hbs index 731f4deb9..538b998aa 100644 --- a/ui/admin/app/components/form/credential/username_password/index.hbs +++ b/ui/admin/app/components/form/credential/username_password/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -46,7 +42,7 @@ {{#if @model.isNew}} {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{#if @model.errors.username}} @@ -103,8 +97,6 @@ @label={{t 'resources.credential.form.password.label'}} @helperText={{t 'resources.credential.form.password.help'}} @error={{@model.errors.password}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/username_password/index.js b/ui/admin/app/components/form/credential/username_password/index.js deleted file mode 100644 index 67d493c10..000000000 --- a/ui/admin/app/components/form/credential/username_password/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormUserNamePasswordCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js index 28c7b2019..65e8c3577 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js @@ -29,15 +29,23 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou 'scopes.scope.credential-stores.credential-store' ); let name, description; + /** + * this sets the credential attribute json_object to a string that + * replicates an empty json blob as CodeMirror expects a string + */ + const json_object = '{}'; + if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); } + return this.store.createRecord('credential', { type, credential_store_id, name, description, + json_object, }); } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 9f3fd2b00..b9adb26ff 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -508,21 +508,18 @@ border-radius: sizing.rems(xxs); height: var(--secret-editor-height); margin-bottom: sizing.rems(l); + width: 100%; .secret-editor-json { .CodeMirror { border-radius: sizing.rems(xxs); height: var(--secret-editor-height); + display: block; } .CodeMirror-gutters { background: var(--secret-editor-gutter); } - - // override CodeMirror padding to add padding to left of line numbers - .CodeMirror-linenumber { - padding: 0; - } } .secret-editor-skeleton-wrapper { diff --git a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs index e80fab03d..29a1c405c 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs @@ -37,7 +37,7 @@ }} - {{#if brokeredCredentialSource.isUnknown}} + {{#if (cannot 'read credential' brokeredCredentialSource)}} {{brokeredCredentialSource.displayName}} {{else}} {{#if brokeredCredentialSource.isStatic}} diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js index 298ee7c1e..ce7f2040d 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; @@ -14,6 +14,8 @@ module( let getCredentialsCount; let getUsernamePasswordCredentialCount; let getUsernameKeyPairCredentialCount; + let getJsonCredentialCount; + let featuresService; const instances = { scopes: { @@ -33,6 +35,7 @@ module( hooks.beforeEach(function () { // Generate resources + featuresService = this.owner.lookup('service:features'); instances.scopes.org = this.server.create('scope', { type: 'org', scope: { id: 'global', type: 'global' }, @@ -50,7 +53,7 @@ module( urls.credentialStores = `${urls.projectScope}/credential-stores`; urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`; urls.credentials = `${urls.staticCredentialStore}/credentials`; - urls.newCredential = `${urls.staticCredentialStore}/credentials/new`; + urls.newCredential = `${urls.credentials}/new`; // Generate resource counter getCredentialsCount = () => { return this.server.schema.credentials.all().models.length; @@ -64,17 +67,23 @@ module( return this.server.schema.credentials.where({ type: 'ssh_private_key' }) .length; }; + getJsonCredentialCount = () => { + return this.server.schema.credentials.where({ type: 'json' }).length; + }; authenticateSession({}); }); - test('Users can create a new username & password credential', async function (assert) { + test('users can create a new username & password credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernamePasswordCredentialCount = getUsernamePasswordCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernamePasswordCredentialCount(), @@ -82,15 +91,18 @@ module( ); }); - test('Users can create a new username & key pair credential', async function (assert) { + test('users can create a new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernameKeyPairCredentialCount = getUsernameKeyPairCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernameKeyPairCredentialCount(), @@ -98,42 +110,85 @@ module( ); }); - test('Users can cancel create new username & password credential', async function (assert) { + test('users can create a new json credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + const jsonCredentialCount = getJsonCredentialCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); + assert.strictEqual(getJsonCredentialCount(), jsonCredentialCount + 1); + }); + + test('users can cancel create new username & password credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users can cancel create new username & key pair credential', async function (assert) { + test('users can cancel create new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users cannot navigate to new credential route without proper authorization', async function (assert) { + test('users can cancel create new json credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('.rose-form-actions [type="button"]'); + + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual(getCredentialsCount(), credentialsCount); + }); + + test('users cannot navigate to new credential route without proper authorization', async function (assert) { assert.expect(2); instances.staticCredentialStore.authorized_collection_actions.credentials = - []; - await visit(urls.staticCredentialStore); - assert.notOk( + instances.staticCredentialStore.authorized_collection_actions.credentials.filter( + (item) => { + item !== 'create'; + } + ); + await visit(urls.credentialStores); + + await click(`[href="${urls.staticCredentialStore}"]`); + + assert.false( instances.staticCredentialStore.authorized_collection_actions.credentials.includes( 'create' ) ); - assert.notOk(find(`[href="${urls.newCredential}"]`)); + assert.dom('.rose-layout-page-actions a').doesNotExist(); }); test('saving a new username & password credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -154,20 +209,21 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-password credential.' - ); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-password credential.'); }); test('saving a new username & key pair credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -188,17 +244,70 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-key-pair credential.' + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-key-pair credential.'); + }); + + test('saving a new json credential with invalid fields displays error messages', async function (assert) { + assert.expect(1); + await visit(urls.credentials); + this.server.post('/credentials', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'Error in provided request.', + details: { + request_fields: [ + { + name: 'attributes.json_object', + description: 'Field required for creating a json credential.', + }, + ], + }, + } + ); + }); + + await click(`[href="${urls.newCredential}"]`); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + }); + + test('cannot navigate to json credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(4); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + + assert.true( + instances.staticCredentialStore.authorized_collection_actions.credentials.includes( + 'create' + ) ); + assert.dom('.hds-form-radio-card').exists({ count: 2 }); + assert + .dom('.hds-form-radio-card:first-child input') + .hasAttribute('value', 'username_password'); + assert + .dom('.hds-form-radio-card:nth-child(2) input') + .hasAttribute('value', 'ssh_private_key'); }); } ); diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index 84f4048a4..9f940eb04 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -15,6 +15,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { let credentialSourceCount; let randomlySelectedCredentialLibraries; let randomlySelectedCredentials; + let featuresService; const instances = { scopes: { @@ -37,11 +38,14 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { target: null, credentialLibraries: null, credentialLibrary: null, + credential: null, + jsonCredential: null, addBrokeredCredentialSources: null, brokeredCredentialSources: null, }; hooks.beforeEach(function () { + featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -60,7 +64,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { type: 'static', scope: instances.scopes.project, }); - instances.credentials = this.server.createList('credential', 2, { + instances.credentials = this.server.createList('credential', 3, { scope: instances.scopes.project, credentialStore: instances.staticCredentialStore, }); @@ -100,6 +104,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { urls.brokeredCredentialSources = `${urls.target}/brokered-credential-sources`; urls.credentialLibrary = `${urls.projectScope}/credential-stores/${instances.credentialLibrary.credentialStoreId}/credential-libraries/${instances.credentialLibrary.id}`; urls.credential = `${urls.projectScope}/credential-stores/${instances.credential.credentialStoreId}/credentials/${instances.credential.id}`; + urls.jsonCredential = `${urls.projectScope}/credential-stores/${instances.credentials[2].credentialStoreId}/credentials/${instances.credentials[2].id}`; urls.addBrokeredCredentialSources = `${urls.target}/add-brokered-credential-sources`; getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; @@ -137,6 +142,19 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { assert.strictEqual(currentURL(), urls.credential); }); + test('cannot navigate to a json type credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(2); + const jsonCredential = instances.credentials[2]; + instances.target.update({ + brokeredCredentialSourceIds: [...randomlySelectedCredentials], + }); + await visit(urls.brokeredCredentialSources); + + assert.dom('.rose-table-row:nth-child(3)').includesText(jsonCredential.name); + assert.dom(`[href="${urls.jsonCredential}"]`).doesNotExist(); + }); + test('visiting add brokered credential sources', async function (assert) { assert.expect(1); await visit(urls.addBrokeredCredentialSources); @@ -157,7 +175,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { test('displays list of brokered credential sources with only credential libraries available', async function (assert) { assert.expect(2); instances.target.update({ - brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], + brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); await visit(urls.addBrokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, getCredentialLibraryCount()); @@ -300,12 +318,13 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], }); const credentialLibraryCount = getCredentialLibraryCount(); + const availableCredentialsCount = getCredentialCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount + 1); + assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); }); test('can remove a username & password type credential', async function (assert) { @@ -314,12 +333,13 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); const credentialCount = getCredentialCount(); + const availableCredentialsCount = getCredentialLibraryCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialCount + 1); + assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); }); test('cannot remove credential libraries without proper authorization', async function (assert) { diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js new file mode 100644 index 000000000..4e76ace86 --- /dev/null +++ b/ui/admin/tests/unit/abilities/credential-test.js @@ -0,0 +1,90 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | credential', function (hooks) { + setupTest(hooks); + + test('canRead', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const credential = { + authorized_actions: ['read'], + type: 'json' + }; + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.ok( + service.can('read credential', credential) + ); + }); + + test('cannot read without proper authorized actions', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const credential = { + authorized_actions: [], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.notOk( + service.can('read credential', credential) + ); + }); + + test('cannot read without proper authorized actions and json-credentials disabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + const credential = { + authorized_actions: [], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.notOk( + service.can('read credential', credential) + ); + }); + + test('cannot read when json-credentials is disabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + const credential = { + authorized_actions: ['read'], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.ok( + service.can('read credential', credential) + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 23144f89f..08691421b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8668,7 +8668,7 @@ babel-types@^6.26.0: babel6-plugin-strip-class-callcheck@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/babel6-plugin-strip-class-callcheck/-/babel6-plugin-strip-class-callcheck-6.0.0.tgz#de841c1abebbd39f78de0affb2c9a52ee228fddf" - integrity sha1-3oQcGr6705943gr/ssmlLuIo/d8= + integrity sha512-biNFJ7JAK4+9BwswDGL0dmYpvXHvswOFR/iKg3Q/f+pNxPEa5bWZkLHI1fW4spPytkHGMe7f/XtYyhzml9hiWg== babylon@^6.18.0: version "6.18.0" @@ -12446,7 +12446,7 @@ ember-cli-terser@^4.0.2: ember-cli-test-info@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-cli-test-info/-/ember-cli-test-info-1.0.0.tgz#ed4e960f249e97523cf891e4aed2072ce84577b4" - integrity sha1-7U6WDySel1I8+JHkrtIHLOhFd7Q= + integrity sha512-dEVTIpmUfCzweC97NGf6p7L6XKBwV2GmSM4elmzKvkttEp5P7AvGA9uGyN4GqFq+RwhW+2b0I2qlX00w+skm+A== dependencies: ember-cli-string-utils "^1.0.0" @@ -20506,7 +20506,7 @@ npm-conf@^1.1.3: npm-git-info@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/npm-git-info/-/npm-git-info-1.0.3.tgz#a933c42ec321e80d3646e0d6e844afe94630e1d5" - integrity sha1-qTPELsMh6A02RuDW6ESv6UYw4dU= + integrity sha512-i5WBdj4F/ULl16z9ZhsJDMl1EQCMQhHZzBwNnKL2LOA+T8IHNeRkLCVz9uVV9SzUdGTbDq+1oXhIYMe+8148vw== npm-install-checks@^3.0.2: version "3.0.2" From 66ae9eb5b07ed645317a9b426038782df5df3f60 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Mon, 12 Dec 2022 18:55:28 -0600 Subject: [PATCH 02/25] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20Update=20byow=20in?= =?UTF-8?q?stall=20boundary=20command=20for=20hcp=20edition=20(#1522)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 🎸 Update byow install boundary command for hcp edition * refactor: 💡 update version in download url for boundary worker --- .../form/worker/create-worker-led/index.js | 6 ++++++ .../tests/acceptance/workers/create-test.js | 20 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/ui/admin/app/components/form/worker/create-worker-led/index.js b/ui/admin/app/components/form/worker/create-worker-led/index.js index b1c804d60..3b7bfdc27 100644 --- a/ui/admin/app/components/form/worker/create-worker-led/index.js +++ b/ui/admin/app/components/form/worker/create-worker-led/index.js @@ -108,6 +108,12 @@ ${listenerText} * @type {string} */ get installBoundaryText() { + if (this.features.isEnabled('byow-pki-hcp-cluster-id')) { + return `wget -q https://releases.hashicorp.com/boundary-worker/0.11.2+hcp/boundary-worker_0.11.2+hcp_linux_amd64.zip ;\\ +sudo apt-get update && sudo apt-get install unzip ;\\ +unzip *.zip ;\\ +./boundary-worker server -config="${this.configFilePath || ''}/pki-worker.hcl"`; + } return `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" ;\\ sudo apt-get update && sudo apt-get install boundary ;\\ diff --git a/ui/admin/tests/acceptance/workers/create-test.js b/ui/admin/tests/acceptance/workers/create-test.js index e49fac281..2f4c9ae6f 100644 --- a/ui/admin/tests/acceptance/workers/create-test.js +++ b/ui/admin/tests/acceptance/workers/create-test.js @@ -60,6 +60,26 @@ module('Acceptance | workers | create', function (hooks) { assert.dom(labels[2]).hasText('Initial Upstreams'); }); + test('download and install step shows correct oss instructions', async function (assert) { + assert.expect(2); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('byow-pki-hcp-cluster-id'); + await visit(newWorkerURL); + const createSection = findAll('.worker-create-section'); + assert.dom(createSection[1]).includesText('curl -fsSL'); + assert.dom(createSection[1]).doesNotIncludeText('wget -q'); + }); + + test('download and install step shows correct hcp instructions', async function (assert) { + assert.expect(2); + const featuresService = this.owner.lookup('service:features'); + featuresService.enable('byow-pki-hcp-cluster-id'); + await visit(newWorkerURL); + const createSection = findAll('.worker-create-section'); + assert.dom(createSection[1]).includesText('wget -q'); + assert.dom(createSection[1]).doesNotIncludeText('curl -fsSL'); + }); + test('Users can navigate to new workers route with proper authorization', async function (assert) { assert.expect(2); await visit(newWorkerURL); From 2192794089bdbbfdce2d178cce064e3fc877f524 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Dec 2022 11:57:36 -0800 Subject: [PATCH 03/25] chore(deps): bump decode-uri-component from 0.2.0 to 0.2.2 (#1510) Bumps [decode-uri-component](https://github.com/SamVerschueren/decode-uri-component) from 0.2.0 to 0.2.2. - [Release notes](https://github.com/SamVerschueren/decode-uri-component/releases) - [Commits](https://github.com/SamVerschueren/decode-uri-component/compare/v0.2.0...v0.2.2) --- updated-dependencies: - dependency-name: decode-uri-component dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Alcaide Corvo --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 08691421b..fe4e9fc09 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11376,9 +11376,9 @@ decimal.js@^10.2.1: integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + version "0.2.2" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.2.tgz#e69dbe25d37941171dd540e024c444cd5188e1e9" + integrity sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ== decompress-response@^6.0.0: version "6.0.0" From fb89cbb4cf4ed7ed54daa08b3f2a350c80a4ca9c Mon Sep 17 00:00:00 2001 From: Devon Anderson Date: Tue, 13 Dec 2022 15:49:22 -0500 Subject: [PATCH 04/25] =?UTF-8?q?fix:=20=F0=9F=90=9B=20refactor=20info-fie?= =?UTF-8?q?ld=20component=20(#1525)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 refactor info-field component Overall context of how the `` was changed to the correct order. Corrected css grid to have the order of `label,input-field,helper-text`. Also refactored all component instances affected by the change. * refactor: 💡 refactor styling for icon to be center refactor will get the icon as close to center as possible. --- .../form/credential-store/static/index.hbs | 7 +++---- .../form/credential-store/vault/index.hbs | 7 +++---- .../components/form/credential/json/index.hbs | 4 ++-- .../form/credential/ssh_private_key/index.hbs | 4 ++-- .../credential/username_password/index.hbs | 4 ++-- .../components/form/target/details/index.hbs | 4 ++-- ui/admin/app/styles/app.scss | 19 +++++++++---------- .../integration/components/info-field-test.js | 12 ++++++------ 8 files changed, 29 insertions(+), 32 deletions(-) diff --git a/ui/admin/app/components/form/credential-store/static/index.hbs b/ui/admin/app/components/form/credential-store/static/index.hbs index 2d44e3dd5..1dbeec0cb 100644 --- a/ui/admin/app/components/form/credential-store/static/index.hbs +++ b/ui/admin/app/components/form/credential-store/static/index.hbs @@ -70,14 +70,13 @@ {{else}} {{t 'form.type.label'}} - {{t - (concat 'resources.credential-store.types.' @model.type) - }} + + {{t (concat 'resources.credential-store.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/credential-store/vault/index.hbs b/ui/admin/app/components/form/credential-store/vault/index.hbs index ed6e1f16c..3bdf964bc 100644 --- a/ui/admin/app/components/form/credential-store/vault/index.hbs +++ b/ui/admin/app/components/form/credential-store/vault/index.hbs @@ -70,14 +70,13 @@ {{else}} {{t 'form.type.label'}} - {{t - (concat 'resources.credential-store.types.' @model.type) - }} + + {{t (concat 'resources.credential-store.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/credential/json/index.hbs b/ui/admin/app/components/form/credential/json/index.hbs index 42f83c0a0..f1f77c226 100644 --- a/ui/admin/app/components/form/credential/json/index.hbs +++ b/ui/admin/app/components/form/credential/json/index.hbs @@ -79,11 +79,11 @@ {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.credential.types.' @model.type)}} + {{t (concat 'resources.credential.help.' @model.type)}} diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs index 471893221..f59cd5f9e 100644 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs +++ b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs @@ -63,11 +63,11 @@ {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.credential.types.' @model.type)}} + {{t (concat 'resources.credential.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/credential/username_password/index.hbs b/ui/admin/app/components/form/credential/username_password/index.hbs index 538b998aa..20a7dee8c 100644 --- a/ui/admin/app/components/form/credential/username_password/index.hbs +++ b/ui/admin/app/components/form/credential/username_password/index.hbs @@ -63,11 +63,11 @@ {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.credential.types.' @model.type)}} + {{t (concat 'resources.credential.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/target/details/index.hbs b/ui/admin/app/components/form/target/details/index.hbs index 5c4ed8237..fb6c93641 100644 --- a/ui/admin/app/components/form/target/details/index.hbs +++ b/ui/admin/app/components/form/target/details/index.hbs @@ -64,12 +64,12 @@ {{else}} {{t 'form.type.label'}} - {{t (concat 'resources.target.types.' @model.type)}} + {{t (concat 'resources.target.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index b9adb26ff..1a18169ac 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -458,7 +458,7 @@ .info-field { .hds-form-field--layout-vertical { - grid-template-areas: 'label' 'helper-text' 'text-input'; + grid-template-areas: 'label' 'text-input' 'helper-text'; .hds-form-label { font-size: sizing.rems(m); @@ -466,26 +466,25 @@ line-height: sizing.rems(m) + sizing.rems(xxs); } - .hds-form-helper-text { - color: var(--token-form-label-color); - font-size: sizing.rems(m); - grid-area: helper-text; - } - .hds-form-text-input { background-color: transparent; border: 0; grid-area: text-input; - line-height: sizing.rems(m) + sizing.rems(xxs); - margin-top: sizing.rems(xs); + font-size: sizing.rems(m); padding: 0; } + + .hds-form-helper-text { + color: var(--token-form-label-color); + line-height: sizing.rems(m) + sizing.rems(xxs); + grid-area: helper-text; + } } &.has-icon { .flight-icon { position: absolute; - transform: translatey(sizing.rems(xl) - sizing.rems(s)); + transform: translatey(sizing.rems(xl) - sizing.rems(xs)); } .hds-form-helper-text, diff --git a/ui/admin/tests/integration/components/info-field-test.js b/ui/admin/tests/integration/components/info-field-test.js index 691c9d04a..ba3a61b84 100644 --- a/ui/admin/tests/integration/components/info-field-test.js +++ b/ui/admin/tests/integration/components/info-field-test.js @@ -11,15 +11,15 @@ module('Integration | Component | info-field', function (hooks) { await render( hbs` - + Type - JSON + Description ` ); assert.dom('.info-field').isVisible(); assert.dom('.info-field .hds-form-label').hasText('Type'); - assert.dom('.info-field .hds-form-helper-text').hasText('JSON'); + assert.dom('.info-field .hds-form-helper-text').hasText('Description'); assert.dom('.info-field .hds-form-text-input').isVisible(); }); @@ -28,9 +28,9 @@ module('Integration | Component | info-field', function (hooks) { await render( hbs` - + Type - JSON + Description ` ); @@ -38,7 +38,7 @@ module('Integration | Component | info-field', function (hooks) { assert.dom('.info-field .hds-form-label').hasText('Type'); assert .dom('.info-field .hds-form-helper-text:last-of-type') - .hasText('JSON'); + .hasText('Description'); assert.dom('[data-test-icon="apple"]').isVisible(); assert.dom('.info-field .hds-form-text-input').isVisible(); }); From d98a85e2f46c6faee5ecb3e8b6751cb9c19c4b89 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Tue, 13 Dec 2022 15:56:03 -0600 Subject: [PATCH 05/25] =?UTF-8?q?test:=20=F0=9F=92=8D=20update=20list=20cr?= =?UTF-8?q?edential=20tests=20(#1528)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 update list credential tests ✅ Closes: ICU-6807 * refactor: 💡 fix JSON variable naming --- .../credential-store/credentials/list-test.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js index 84e10e605..61ae810e9 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/list-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/list-test.js @@ -16,6 +16,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -50,6 +51,11 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -75,7 +81,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { .isVisible({ count: this.server.schema.credentials.all().models.length }); }); - test('User cannot navigate to index without either list or create action', async function (assert) { + test('User cannot navigate to Credentials tab without either list or create action', async function (assert) { assert.expect(3); instances.staticCredentialStore.authorized_collection_actions.credentials = []; @@ -93,7 +99,7 @@ module('Acceptance | credential-stores | credentials | list', function (hooks) { assert.dom('.rose-nav-tabs a:nth-child(2)').doesNotExist(); }); - test('User can navigate to index with only create action', async function (assert) { + test('User can navigate to new credential screen with only create action', async function (assert) { assert.expect(2); instances.staticCredentialStore.authorized_collection_actions.credentials = ['create']; From 7d3727dc343187ed72685a801b11a1be2018e9f5 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Tue, 13 Dec 2022 17:45:26 -0800 Subject: [PATCH 06/25] Radio card migration - hostcatalog hosts host sets (#1503) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 radio card migration --- addons/api/addon/models/host-catalog.js | 5 +- addons/api/addon/models/host-set.js | 4 +- addons/core/translations/en-us.yaml | 1 - addons/core/translations/form/en-us.yaml | 3 +- addons/core/translations/resources/en-us.yaml | 4 +- .../form/credential/ssh_private_key/index.hbs | 2 +- .../credential/username_password/index.hbs | 2 +- .../form/host-catalog/aws/index.hbs | 91 +++++++--------- .../components/form/host-catalog/aws/index.js | 16 +++ .../form/host-catalog/azure/index.hbs | 102 ++++++++---------- .../form/host-catalog/azure/index.js | 20 ++++ .../form/host-catalog/static/index.hbs | 89 +++++++-------- .../form/host-catalog/static/index.js | 20 ++++ .../components/form/host-set/aws/index.hbs | 31 ++---- .../components/form/host-set/azure/index.hbs | 31 ++---- .../app/components/form/host/aws/index.hbs | 29 ++--- .../app/components/form/host/azure/index.hbs | 30 ++---- .../form/onboarding/choose-path/index.hbs | 49 +++++---- .../components/form/target/details/index.hbs | 2 +- .../acceptance/host-catalogs/create-test.js | 39 +++++-- .../host-catalogs/host-sets/create-test.js | 6 +- .../host-catalogs/host-sets/update-test.js | 12 +-- .../acceptance/onboarding/choose-path-test.js | 8 +- .../scope/projects/targets/target/hosts.hbs | 2 +- 24 files changed, 308 insertions(+), 290 deletions(-) create mode 100644 ui/admin/app/components/form/host-catalog/azure/index.js create mode 100644 ui/admin/app/components/form/host-catalog/static/index.js diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index 08e1a7d82..e4b78e95c 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -1,6 +1,7 @@ import GeneratedHostCatalogModel from '../generated/models/host-catalog'; -export const types = ['aws', 'azure']; +export const types = ['static', 'plugin']; +export const pluginTypes = ['aws', 'azure']; export default class HostCatalogModel extends GeneratedHostCatalogModel { // =attributes @@ -26,7 +27,7 @@ export default class HostCatalogModel extends GeneratedHostCatalogModel { * @type {boolean} */ get isUnknown() { - return this.isPlugin && !types.includes(this.plugin?.name); + return this.isPlugin && !pluginTypes.includes(this.plugin?.name); } /** diff --git a/addons/api/addon/models/host-set.js b/addons/api/addon/models/host-set.js index ed1c0127f..77c3c6c7b 100644 --- a/addons/api/addon/models/host-set.js +++ b/addons/api/addon/models/host-set.js @@ -1,5 +1,5 @@ import GeneratedHostSetModel from '../generated/models/host-set'; -import { types } from './host-catalog'; +import { pluginTypes } from './host-catalog'; export default class HostSetModel extends GeneratedHostSetModel { // =attributes @@ -25,7 +25,7 @@ export default class HostSetModel extends GeneratedHostSetModel { * @type {boolean} */ get isUnknown() { - return this.isPlugin && !types.includes(this.plugin?.name); + return this.isPlugin && !pluginTypes.includes(this.plugin?.name); } /** diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index efa8a811c..35787d6d8 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -41,7 +41,6 @@ descriptions: neither-list-nor-create: You do not have permission to list or create {resource}. no-results: We found no matching records. choose-a-provider: There are several built-in plugins for the most common providers. You can choose from one of the included plugins or use a custom plugin. - choose-a-provider-azure: Boundary does not yet support rotating credentials. provider: This is the provider for this host catalog. The format of the host set filters are specific to each provider. questions: delete-confirm: Are you sure you want to delete this resource? diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index 8a2286444..682bd73f8 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -150,6 +150,7 @@ secret: help: The client secret value generated by Azure for Boundary to use for authentication. disable_credential_rotation: label: Disable credential rotation + help: Azure host catalogs do not yet support credential rotation. dns_names: label: Latest DNS Names host: @@ -163,4 +164,4 @@ tags: label: Tags secret-editor: details: This secret is saved but won't be displayed - edit: Click to replace this secret \ No newline at end of file + edit: Click to replace this secret diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index b2bfb2a95..46f93a131 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -93,13 +93,13 @@ host-catalog: delete: Delete Host Catalog help: static: Provide a list of hosts and host sets with static addresses - dynamic: Automate discovery of target hosts and services on Azure or AWS + plugin: Automate discovery of target hosts and services on Azure or AWS types: static: Static unknown: Plugin aws: AWS azure: Azure - dynamic: Dynamic + plugin: Dynamic form: aws_region: label: AWS Region diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs index f59cd5f9e..bcee4bed2 100644 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs +++ b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs @@ -40,7 +40,7 @@ {{/if}} {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each @types as |credentialType|}} {{#if @model.isNew}} - + {{t 'form.type.label'}} {{#each @types as |credentialType|}} {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{t 'descriptions.choose-a-provider'}} - - - <:title>{{t 'titles.provider'}} - - - - <:title>{{t 'titles.choose-a-provider'}} - <:description>{{t 'descriptions.choose-a-provider'}} - - <:body> - - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + + {{/each-in}} + {{else}} ({ ...obj, [plugin]: icons[i] }), + {} + ); + } // =actions @action diff --git a/ui/admin/app/components/form/host-catalog/azure/index.hbs b/ui/admin/app/components/form/host-catalog/azure/index.hbs index a09a3d411..807b18f84 100644 --- a/ui/admin/app/components/form/host-catalog/azure/index.hbs +++ b/ui/admin/app/components/form/host-catalog/azure/index.hbs @@ -44,67 +44,47 @@ {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - - - - - <:title>{{t 'titles.provider'}} - - - <:title>{{t 'titles.choose-a-provider'}} - <:description> - {{concat + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + + + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{concat (t 'descriptions.choose-a-provider') - ' ' - (t 'descriptions.choose-a-provider-azure') - }} - + }} - <:body> - - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + + + {{/each-in}} + {{else}} + + {{#if (can 'save model' @model)}} ({ ...obj, [plugin]: icons[i] }), + {} + ); + } +} diff --git a/ui/admin/app/components/form/host-catalog/static/index.hbs b/ui/admin/app/components/form/host-catalog/static/index.hbs index 2654c6608..3c2245981 100644 --- a/ui/admin/app/components/form/host-catalog/static/index.hbs +++ b/ui/admin/app/components/form/host-catalog/static/index.hbs @@ -46,60 +46,51 @@ {{#if @model.isNew}} - - <:title>{{t 'form.type.label'}} - <:body> - + {{t 'form.type.label'}} + {{#each this.hostCatalogTypes as |hostCatalogType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' hostCatalogType) + }} + {{t + (concat 'resources.host-catalog.help.' hostCatalogType) + }} + + {{/each}} + {{#if @model.isPlugin}} - - - <:title>{{t 'titles.provider'}} - - - <:title>{{t 'titles.choose-a-provider'}} - <:description>{{t 'descriptions.choose-a-provider'}} - <:body> - + {{t 'titles.provider'}} + {{t 'titles.choose-a-provider'}} + {{t 'descriptions.choose-a-provider'}} + + {{#each-in this.mapResourceTypeWithIcon as |pluginType|}} + - - - - - + {{t + (concat 'resources.host-catalog.types.' pluginType) + }} + {{t + (concat 'resources.host-catalog.help.' pluginType) + }} + + {{/each-in}} + {{/if}} {{/if}} diff --git a/ui/admin/app/components/form/host-catalog/static/index.js b/ui/admin/app/components/form/host-catalog/static/index.js new file mode 100644 index 000000000..c8f2e50f2 --- /dev/null +++ b/ui/admin/app/components/form/host-catalog/static/index.js @@ -0,0 +1,20 @@ +import Component from '@glimmer/component'; +import { types, pluginTypes } from 'api/models/host-catalog'; + +//Note: this is a temporary solution till we have resource type helper in place +const icons = ['aws-color', 'azure-color']; + +export default class FormStaticHostCatalogAwsComponent extends Component { + // =properties + hostCatalogTypes = types; + /** + * maps resource type with icon + * @type {object} + */ + get mapResourceTypeWithIcon() { + return pluginTypes.reduce( + (obj, plugin, i) => ({ ...obj, [plugin]: icons[i] }), + {} + ); + } +} diff --git a/ui/admin/app/components/form/host-set/aws/index.hbs b/ui/admin/app/components/form/host-set/aws/index.hbs index cc131cd79..0f430dabb 100644 --- a/ui/admin/app/components/form/host-set/aws/index.hbs +++ b/ui/admin/app/components/form/host-set/aws/index.hbs @@ -53,26 +53,17 @@ @disabled={{true}} /> - - <:title>{{t 'titles.provider'}} - <:description>{{t 'descriptions.provider'}} - - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.aws'}} + + - - <:title>{{t 'titles.provider'}} - <:description>{{t 'descriptions.provider'}} - - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.azure'}} + + - - <:title>{{t 'titles.provider'}} - <:body> - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.aws'}} + + <:title>{{t 'form.ip_addresses.label'}} diff --git a/ui/admin/app/components/form/host/azure/index.hbs b/ui/admin/app/components/form/host/azure/index.hbs index c87c94843..348fe2788 100644 --- a/ui/admin/app/components/form/host/azure/index.hbs +++ b/ui/admin/app/components/form/host/azure/index.hbs @@ -52,25 +52,17 @@ {{/if}} - - <:title>{{t 'titles.provider'}} - <:body> - - - - - - + + {{t 'titles.provider'}} + + {{t 'resources.host-catalog.types.azure'}} + + <:title>{{t 'form.ip_addresses.label'}} <:body> diff --git a/ui/admin/app/components/form/onboarding/choose-path/index.hbs b/ui/admin/app/components/form/onboarding/choose-path/index.hbs index 9e91d69e9..1c4a59b96 100644 --- a/ui/admin/app/components/form/onboarding/choose-path/index.hbs +++ b/ui/admin/app/components/form/onboarding/choose-path/index.hbs @@ -5,26 +5,33 @@ @onSubmit={{fn @submit this.path}} as |form| > - -
- - -
-
+ + + + {{t 'onboarding.quick-start.titles.quick-setup'}} + {{t + 'onboarding.quick-start.descriptions.quick-setup' + }} + + + {{t 'onboarding.quick-start.titles.manual'}} + {{t + 'onboarding.quick-start.descriptions.manual' + }} + + + \ No newline at end of file diff --git a/ui/admin/app/components/form/target/details/index.hbs b/ui/admin/app/components/form/target/details/index.hbs index fb6c93641..9bdb90a2b 100644 --- a/ui/admin/app/components/form/target/details/index.hbs +++ b/ui/admin/app/components/form/target/details/index.hbs @@ -42,7 +42,7 @@ {{#if (and @model.isNew (feature-flag 'ssh-target'))}} - + {{t 'form.type.label'}} {{#each-in this.mapResourceTypeWithIcon as |targetType icon|}} this.server.schema.hostCatalogs.all().models.length; @@ -81,18 +83,29 @@ module('Acceptance | host-catalogs | create', function (hooks) { await visit(urls.newStaticHostCatalog); await fillIn('[name="name"]', 'random string'); await fillIn('[name="description"]', 'random string'); - await fillIn('[name="types"]', 'static'); + await fillIn('[name="type"]', 'static'); await click('[type="submit"]'); assert.strictEqual(gethostCatalogCount(), count + 1); }); - test('Users can create new dynamic host catalogs', async function (assert) { + test('Users can create new dynamic aws host catalogs with aws provider', async function (assert) { assert.expect(1); const count = gethostCatalogCount(); - await visit(urls.newDynamicHostCatalog); + await visit(urls.newAWSDynamicHostCatalog); await fillIn('[name="name"]', 'random string'); await fillIn('[name="description"]', 'random string'); - await fillIn('[name="types"]', 'aws'); + await fillIn('[name="type"]', 'aws'); + await click('[type="submit"]'); + assert.strictEqual(gethostCatalogCount(), count + 1); + }); + + test('Users can create new dynamic aws host catalogs with azure provider', async function (assert) { + assert.expect(1); + const count = gethostCatalogCount(); + await visit(urls.newAzureDynamicHostCatalog); + await fillIn('[name="name"]', 'random string'); + await fillIn('[name="description"]', 'random string'); + await fillIn('[name="type"]', 'azure'); await click('[type="submit"]'); assert.strictEqual(gethostCatalogCount(), count + 1); }); @@ -107,10 +120,20 @@ module('Acceptance | host-catalogs | create', function (hooks) { assert.strictEqual(gethostCatalogCount(), count); }); - test('Users can cancel creation of new dynamic host catalogs', async function (assert) { + test('Users can cancel creation of new dynamic host catalogs with AWS provider', async function (assert) { assert.expect(2); const count = gethostCatalogCount(); - await visit(urls.newDynamicHostCatalog); + await visit(urls.newAWSDynamicHostCatalog); + await fillIn('[name="name"]', 'random string'); + await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.hostCatalogs); + assert.strictEqual(gethostCatalogCount(), count); + }); + + test('Users can cancel creation of new dynamic host catalogs with Azure provider', async function (assert) { + assert.expect(2); + const count = gethostCatalogCount(); + await visit(urls.newAzureDynamicHostCatalog); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); assert.strictEqual(currentURL(), urls.hostCatalogs); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js index 6aa8fe830..cc0c8891e 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/create-test.js @@ -97,9 +97,9 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { const name = 'aws host set'; await fillIn('[name="name"]', name); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filters"]', 'filter'); - await click('form fieldset:nth-of-type(3) [title="Add"]'); + await click('form fieldset:nth-of-type(2) [title="Add"]'); await fillIn('[name="sync_interval_seconds"]', 10); await click('[type="submit"]'); @@ -128,7 +128,7 @@ module('Acceptance | host-catalogs | host sets | create', function (hooks) { const name = 'azure host set'; await fillIn('[name="name"]', name); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filter"]', 'filter'); await fillIn('[name="sync_interval_seconds"]', 10); await click('[type="submit"]'); diff --git a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js index 46864ff1c..669ae7425 100644 --- a/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js +++ b/ui/admin/tests/acceptance/host-catalogs/host-sets/update-test.js @@ -147,20 +147,20 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { await fillIn('[name="name"]', name); // Remove all the preferred endpoints await Promise.all( - findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(1) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); // Remove all the filters await Promise.all( - findAll('form fieldset:nth-of-type(3) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="filters"]', 'filter'); - await click('form fieldset:nth-of-type(3) [title="Add"]'); + await click('form fieldset:nth-of-type(2) [title="Add"]'); await fillIn('[name="sync_interval_seconds"]', 10); await click('.rose-form-actions [type="submit"]'); @@ -198,12 +198,12 @@ module('Acceptance | host-catalogs | host sets | update', function (hooks) { await fillIn('[name="name"]', name); // Remove all the preferred endpoints await Promise.all( - findAll('form fieldset:nth-of-type(2) [title="Remove"]').map((element) => + findAll('form fieldset:nth-of-type(1) [title="Remove"]').map((element) => click(element) ) ); await fillIn('[name="preferred_endpoints"]', 'endpoint'); - await click('form fieldset:nth-of-type(2) [title="Add"]'); + await click('form fieldset:nth-of-type(1) [title="Add"]'); await fillIn('[name="filter"]', 'filter'); await fillIn('[name="sync_interval_seconds"]', 10); await click('.rose-form-actions [type="submit"]'); diff --git a/ui/admin/tests/acceptance/onboarding/choose-path-test.js b/ui/admin/tests/acceptance/onboarding/choose-path-test.js index 7c4226850..836892631 100644 --- a/ui/admin/tests/acceptance/onboarding/choose-path-test.js +++ b/ui/admin/tests/acceptance/onboarding/choose-path-test.js @@ -21,15 +21,15 @@ module('Acceptance | onboarding | choose-path', function (hooks) { test('defaults to the guided path', async function (assert) { assert.expect(2); await visit(urls.choosePath); - assert.strictEqual(find('input[name=paths]:checked').value, 'guided'); + assert.strictEqual(find('input[name=path]:checked').value, 'guided'); assert.false(find('input[value=manual]').checked); }); test('updates to the manual path when manual card clicked', async function (assert) { assert.expect(2); await visit(urls.choosePath); - await click('[name="paths"][value="manual"]'); - assert.strictEqual(find('input[name=paths]:checked').value, 'manual'); + await click('[name="path"][value="manual"]'); + assert.strictEqual(find('input[name=path]:checked').value, 'manual'); assert.false(find('input[value=guided]').checked); }); @@ -43,7 +43,7 @@ module('Acceptance | onboarding | choose-path', function (hooks) { test('guided path directs user to /global/new route', async function (assert) { assert.expect(1); await visit(urls.choosePath); - await click('[name="paths"][value="manual"]'); + await click('[name="path"][value="manual"]'); await click('[type="submit"]'); assert.strictEqual(currentURL(), urls.newOrg); }); diff --git a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs index 56ee4d216..33d9fe558 100644 --- a/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs +++ b/ui/desktop/app/templates/scopes/scope/projects/targets/target/hosts.hbs @@ -30,7 +30,7 @@ {{#if host.isStatic}} {{host.address}} {{else}} - + {{/if}} From 88f195d02dd0f045c9ef2287d6c8e40e9264b2a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 01:04:54 -0500 Subject: [PATCH 07/25] chore(deps): bump qs from 6.5.2 to 6.5.3 in /ui/desktop/electron-app (#1513) Bumps [qs](https://github.com/ljharb/qs) from 6.5.2 to 6.5.3. - [Release notes](https://github.com/ljharb/qs/releases) - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.5.2...v6.5.3) --- updated-dependencies: - dependency-name: qs dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Alcaide Corvo Co-authored-by: Randall Morey --- ui/desktop/electron-app/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/desktop/electron-app/yarn.lock b/ui/desktop/electron-app/yarn.lock index eb0ed532d..570b7b72c 100644 --- a/ui/desktop/electron-app/yarn.lock +++ b/ui/desktop/electron-app/yarn.lock @@ -3262,9 +3262,9 @@ punycode@^2.1.0, punycode@^2.1.1: integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== + version "6.5.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.3.tgz#3aeeffc91967ef6e35c0e488ef46fb296ab76aad" + integrity sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA== queue-microtask@^1.2.2: version "1.2.3" From 3c2d5e6c7ae74a1531309b4c45ee6021777e6031 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Dec 2022 03:16:16 -0500 Subject: [PATCH 08/25] chore(deps): bump express from 4.17.1 to 4.18.2 (#1514) Bumps [express](https://github.com/expressjs/express) from 4.17.1 to 4.18.2. - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.17.1...4.18.2) --- updated-dependencies: - dependency-name: express dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Carlos Alcaide Corvo Co-authored-by: Randall Morey --- yarn.lock | 257 ++++++++++-------------------------------------------- 1 file changed, 48 insertions(+), 209 deletions(-) diff --git a/yarn.lock b/yarn.lock index fe4e9fc09..977938cf5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7440,15 +7440,7 @@ abortcontroller-polyfill@^1.7.3: resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== -accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd" - integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA== - dependencies: - mime-types "~2.1.24" - negotiator "0.6.2" - -accepts@~1.3.8: +accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -7910,7 +7902,7 @@ array-find-index@^1.0.1, array-find-index@^1.0.2: array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" - integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== array-includes@^3.0.3: version "3.1.3" @@ -8823,26 +8815,10 @@ bn.js@^5.0.0, bn.js@^5.1.1: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== -body-parser@1.19.0, body-parser@^1.17.0, body-parser@^1.19.0: - version "1.19.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" - integrity sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw== - dependencies: - bytes "3.1.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "1.7.2" - iconv-lite "0.4.24" - on-finished "~2.3.0" - qs "6.7.0" - raw-body "2.4.0" - type-is "~1.6.17" - -body-parser@1.20.0: - version "1.20.0" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.0.tgz#3de69bd89011c11573d7bfee6a64f11b6bd27cc5" - integrity sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg== +body-parser@1.20.1, body-parser@^1.17.0, body-parser@^1.19.0: + version "1.20.1" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" + integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== dependencies: bytes "3.1.2" content-type "~1.0.4" @@ -8852,7 +8828,7 @@ body-parser@1.20.0: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.10.3" + qs "6.11.0" raw-body "2.5.1" type-is "~1.6.18" unpipe "1.0.0" @@ -9728,11 +9704,6 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= -bytes@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" - integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== - bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" @@ -10749,13 +10720,6 @@ constants-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" - integrity sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g== - dependencies: - safe-buffer "5.1.2" - content-disposition@0.5.4: version "0.5.4" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" @@ -10788,12 +10752,7 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.0, convert-source-map@^1.5.1, cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= - -cookie@0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" - integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== cookie@0.5.0: version "0.5.0" @@ -11496,11 +11455,6 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= - detab@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detab/-/detab-2.0.4.tgz#b927892069aff405fbb9a186fe97a44a92a94b43" @@ -11876,7 +11830,7 @@ editor@~1.0.0: ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= + integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== electron-notarize@^1.1.1: version "1.2.1" @@ -13300,7 +13254,7 @@ emotion-theming@^10.0.27: encodeurl@^1.0.2, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" - integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= + integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== encoding@^0.1.11, encoding@^0.1.12: version "0.1.13" @@ -13636,7 +13590,7 @@ escape-goat@^2.0.0: escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" - integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= + integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: version "2.0.0" @@ -13984,7 +13938,7 @@ esutils@^2.0.2: etag@~1.8.1: version "1.8.1" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= + integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== event-emitter@~0.3.5: version "0.3.5" @@ -14183,50 +14137,14 @@ expect@^27.4.6: jest-matcher-utils "^27.4.6" jest-message-util "^27.4.6" -express@^4.10.7, express@^4.13.1, express@^4.17.1: - version "4.17.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" - integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g== - dependencies: - accepts "~1.3.7" - array-flatten "1.1.1" - body-parser "1.19.0" - content-disposition "0.5.3" - content-type "~1.0.4" - cookie "0.4.0" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "~1.1.2" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.3" - path-to-regexp "0.1.7" - proxy-addr "~2.0.5" - qs "6.7.0" - range-parser "~1.2.1" - safe-buffer "5.1.2" - send "0.17.1" - serve-static "1.14.1" - setprototypeof "1.1.1" - statuses "~1.5.0" - type-is "~1.6.18" - utils-merge "1.0.1" - vary "~1.1.2" - -express@^4.17.2: - version "4.18.1" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.1.tgz#7797de8b9c72c857b9cd0e14a5eea80666267caf" - integrity sha512-zZBcOX9TfehHQhtupq57OF8lFZ3UZi08Y97dwFCkD8p9d/d2Y3M+ykKcwaMDEL+4qyUolgBDX6AblpR3fL212Q== +express@^4.10.7, express@^4.13.1, express@^4.17.1, express@^4.17.2: + version "4.18.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" + integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.0" + body-parser "1.20.1" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.5.0" @@ -14245,7 +14163,7 @@ express@^4.17.2: parseurl "~1.3.3" path-to-regexp "0.1.7" proxy-addr "~2.0.7" - qs "6.10.3" + qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" send "0.18.0" @@ -14617,7 +14535,7 @@ filter-obj@^1.1.0: resolved "https://registry.yarnpkg.com/filter-obj/-/filter-obj-1.1.0.tgz#9b311112bc6c6127a16e016c6c5d7f19e0805c5b" integrity sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ== -finalhandler@1.1.2, finalhandler@~1.1.2: +finalhandler@1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.2.tgz#b7e7d000ffd11938d0fdb053506f6ebabe9f587d" integrity sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA== @@ -14960,7 +14878,7 @@ fragment-cache@^0.2.1: fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= + integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== from2@^1.3.0: version "1.3.0" @@ -15311,7 +15229,16 @@ get-installed-path@^2.0.3: dependencies: global-modules "1.0.0" -get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: +get-intrinsic@^1.0.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" + integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + dependencies: + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.3" + +get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== @@ -16225,17 +16152,6 @@ http-cache-semantics@^4.0.0, http-cache-semantics@^4.1.0: resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz#49e91c5cbf36c9b94bcfcd71c23d5249ec74e390" integrity sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ== -http-errors@1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" - integrity sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg== - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -16257,17 +16173,6 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -http-errors@~1.7.2: - version "1.7.3" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.3.tgz#6c619e4f9c60308c38519498c14fbb10aacebb06" - integrity sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw== - dependencies: - depd "~1.1.2" - inherits "2.0.4" - setprototypeof "1.1.1" - statuses ">= 1.5.0 < 2" - toidentifier "1.0.0" - http-parser-js@>=0.5.1: version "0.5.3" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.3.tgz#01d2709c79d41698bb01d4decc5e9da4e4a033d9" @@ -19530,7 +19435,7 @@ meant@^1.0.2: media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" - integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== mem@^4.0.0, mem@^4.3.0: version "4.3.0" @@ -19620,7 +19525,7 @@ meow@^9.0.0: merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== merge-stream@^2.0.0: version "2.0.0" @@ -19648,7 +19553,7 @@ merge@^1.2.0, merge@^2.1.0, merge@^2.1.1: methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== microevent.ts@~0.1.1: version "0.1.1" @@ -19780,7 +19685,7 @@ mime-db@1.52.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.7: +mime-types@^2.1.11, mime-types@^2.1.12, mime-types@^2.1.18, mime-types@^2.1.26, mime-types@^2.1.27, mime-types@~2.1.7: version "2.1.32" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5" integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A== @@ -19794,7 +19699,7 @@ mime-types@^2.1.25: dependencies: mime-db "1.50.0" -mime-types@~2.1.34: +mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -20124,7 +20029,7 @@ move-concurrently@^1.0.1: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== ms@2.1.1: version "2.1.1" @@ -20198,11 +20103,6 @@ ncp@^2.0.0: resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3" integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M= -negotiator@0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" - integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== - negotiator@0.6.3, negotiator@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" @@ -20862,12 +20762,12 @@ object-hash@^1.3.1: resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-1.3.1.tgz#fde452098a951cb145f039bb7d455449ddc126df" integrity sha512-OSuu/pU4ENM9kmREg0BdNrUDIl1heYa4mBZacJc+vVWz4GtAwu7jO8s4AIt2aGRUTqxykpWzI3Oqnsm13tTMDA== -object-inspect@^1.11.0, object-inspect@^1.9.0: +object-inspect@^1.11.0: version "1.11.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.11.0.tgz#9dceb146cedd4148a0d9e51ab88d34cf509922b1" integrity sha512-jp7ikS6Sd3GxQfZJPyH3cjcbJF6GZPClgdV+EFygjFLQ5FmW/dRUnTd9PQ9k0JhoNDabWFbpF1yCdSWCC6gexg== -object-inspect@^1.12.2: +object-inspect@^1.12.2, object-inspect@^1.9.0: version "1.12.2" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== @@ -21536,7 +21436,7 @@ path-root@^0.1.1: path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== path-to-regexp@^1.7.0: version "1.8.0" @@ -22233,7 +22133,7 @@ protoduck@^5.0.1: dependencies: genfun "^5.0.0" -proxy-addr@~2.0.5, proxy-addr@~2.0.7: +proxy-addr@~2.0.7: version "2.0.7" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== @@ -22341,22 +22241,10 @@ qrcode-terminal@^0.12.0: resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" integrity sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ== -qs@6.10.3: - version "6.10.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.3.tgz#d6cde1b2ffca87b5aa57889816c5f81535e22e8e" - integrity sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ== - dependencies: - side-channel "^1.0.4" - -qs@6.7.0: - version "6.7.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" - integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== - -qs@^6.10.0, qs@^6.4.0: - version "6.10.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.10.1.tgz#4931482fa8d647a5aab799c5271d2133b981fb6a" - integrity sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg== +qs@6.11.0, qs@^6.10.0, qs@^6.4.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== dependencies: side-channel "^1.0.4" @@ -22463,16 +22351,6 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.4.0.tgz#a1ce6fb9c9bc356ca52e89256ab59059e13d0332" - integrity sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q== - dependencies: - bytes "3.1.0" - http-errors "1.7.2" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-body@2.5.1: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" @@ -23886,25 +23764,6 @@ semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" -send@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" - integrity sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg== - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.7.2" - mime "1.6.0" - ms "2.1.1" - on-finished "~2.3.0" - range-parser "~1.2.1" - statuses "~1.5.0" - send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -23963,16 +23822,6 @@ serve-favicon@^2.5.0: parseurl "~1.3.2" safe-buffer "5.1.1" -serve-static@1.14.1: - version "1.14.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" - integrity sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg== - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.3" - send "0.17.1" - serve-static@1.15.0: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" @@ -24006,11 +23855,6 @@ setprototypeof@1.1.0: resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== -setprototypeof@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" - integrity sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw== - setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -24614,7 +24458,7 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0: +"statuses@>= 1.4.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= @@ -25744,11 +25588,6 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= -toidentifier@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" - integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== - toidentifier@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" @@ -25960,7 +25799,7 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-is@~1.6.17, type-is@~1.6.18: +type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== @@ -26247,7 +26086,7 @@ universalify@^2.0.0: unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" - integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= + integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== unquote@^1.1.0, unquote@~1.1.1: version "1.1.1" @@ -26465,7 +26304,7 @@ utila@~0.4: utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= + integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== uuid-browser@^3.1.0: version "3.1.0" From 8a61c282547ecc442061f63672333b2eb4417167 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Wed, 14 Dec 2022 09:53:34 -0600 Subject: [PATCH 09/25] =?UTF-8?q?test:=20=F0=9F=92=8D=20Create=20read=20te?= =?UTF-8?q?sts=20for=20JSON=20credentials=20(#1524)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 Create read tests for JSON credentials ✅ Closes: ICU-6804 * refactor: 💡 use HDS color token to skeleton message color * refactor: 💡 update JSON variable names --- ui/admin/app/styles/app.scss | 2 +- .../credential-store/credentials/read-test.js | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 1a18169ac..a2a099733 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -525,7 +525,7 @@ // using ui-gray directly so it does not change when // user is in dark mode since the secret editor // does not change theme colors between light and dark - color: #{color(ui-gray)}; + color: var(--token-color-palette-neutral-300); display: flex; height: 100%; position: relative; diff --git a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js index ff67baa3d..2e8f482ed 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/read-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/read-test.js @@ -17,6 +17,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -26,6 +27,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, unknownCredential: null, }; @@ -53,6 +55,11 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -60,6 +67,7 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; urls.unknownCredential = `${urls.credentials}/foo`; authenticateSession({}); }); @@ -86,6 +94,17 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { assert.strictEqual(currentURL(), urls.usernameKeyPairCredential); }); + test('visiting JSON credential', async function (assert) { + assert.expect(2); + await visit(urls.staticCredentialStore); + await click(`[href="${urls.credentials}"]`); + await a11yAudit(); + assert.strictEqual(currentURL(), urls.credentials); + await click(`[href="${urls.jsonCredential}"]`); + await a11yAudit(); + assert.strictEqual(currentURL(), urls.jsonCredential); + }); + test('cannot navigate to a username & password credential form without proper authorization', async function (assert) { assert.expect(1); instances.usernamePasswordCredential.authorized_actions = @@ -106,6 +125,24 @@ module('Acceptance | credential-stores | credentials | read', function (hooks) { assert.dom('.rose-table-body tr:nth-child(2) a').doesNotExist(); }); + test('cannot navigate to a JSON credential form without proper authorization', async function (assert) { + assert.expect(1); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item != 'read' + ); + await visit(urls.credentials); + assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + }); + + test('cannot navigate to a JSON credential form when feature not enabled', async function (assert) { + assert.expect(1); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + await visit(urls.credentials); + assert.dom('.rose-table-body tr:nth-child(3) a').doesNotExist(); + }); + test('visiting an unknown credential displays 404 message', async function (assert) { assert.expect(1); await visit(urls.unknownCredential); From c4a70e9e9e645c593d5e73f14e2ee34698de0d39 Mon Sep 17 00:00:00 2001 From: Devon Anderson Date: Wed, 14 Dec 2022 12:22:44 -0500 Subject: [PATCH 10/25] Icu 7083 add stylelint to admin (#1519) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Added styelint to `ui/admin' * refactor: 💡 quick refactor Refactor attribute name back to original ✅ Closes: ICU-7083 * Update _notify.scss for got to set the calling of the animation back in the selector * refactor: 💡 quick refactor for stylelintrc file possible pattern selector settings * refactor: 💡 added rule for selector class pattern Added rule for kebab-case, HDS, and CodeMirror ✅ Closes: ICU-7083 * Update app.scss fixed linting errors. * refactor: 💡 refactor stylelintrc Simplify regex for `stylelintrc` and added comments that details what the regex is doing. ✅ Closes: ICU-7083 * Update ui/admin/.stylelintrc.js Co-authored-by: Zhihe Li Co-authored-by: Randall Morey Co-authored-by: Zhihe Li --- ui/admin/.eslintrc.js | 1 + ui/admin/.sasslintrc | 8 - ui/admin/.stylelintrc.js | 33 +++ ui/admin/app/styles/_notify.scss | 9 +- ui/admin/app/styles/app.scss | 17 +- ui/admin/package.json | 8 +- yarn.lock | 387 ++----------------------------- 7 files changed, 74 insertions(+), 389 deletions(-) delete mode 100644 ui/admin/.sasslintrc create mode 100644 ui/admin/.stylelintrc.js diff --git a/ui/admin/.eslintrc.js b/ui/admin/.eslintrc.js index 8ea8cb7b4..bd758f6a0 100644 --- a/ui/admin/.eslintrc.js +++ b/ui/admin/.eslintrc.js @@ -32,6 +32,7 @@ module.exports = { '.eslintrc.js', '.prettierrc.js', '.template-lintrc.js', + '.stylelintrc.js', 'ember-cli-build.js', 'testem.js', 'blueprints/*/index.js', diff --git a/ui/admin/.sasslintrc b/ui/admin/.sasslintrc deleted file mode 100644 index 76ed8341e..000000000 --- a/ui/admin/.sasslintrc +++ /dev/null @@ -1,8 +0,0 @@ -files: - include: 'app/styles/**/*.s+(a|c)ss' -rules: - leading-zero: 0 - mixins-before-declarations: 0 - nesting-depth: 0 - placeholder-in-extend: 0 - property-sort-order: 0 diff --git a/ui/admin/.stylelintrc.js b/ui/admin/.stylelintrc.js new file mode 100644 index 000000000..cd5887c5f --- /dev/null +++ b/ui/admin/.stylelintrc.js @@ -0,0 +1,33 @@ +'use strict'; + +module.exports = { + extends: ['stylelint-config-standard-scss', 'stylelint-config-prettier-scss'], + rules: { + // We oftentimes want to have long form properties for readability + 'declaration-block-no-redundant-longhand-properties': null, + + // This is just a personal preference unless the quotes are needed + // to escape special characters + 'function-url-quotes': null, + + // This is often limited by the DOM and we want to order our declarations by our own standard + 'no-descending-specificity': null, + + // -Webkit animation CSS for older browsers. Need a decision to remove `-webkit` syntax in + // favor of normal CSS + 'property-no-vendor-prefix': null, + 'at-rule-no-vendor-prefix': null, + + // As a team we want to stick to using kebab-case as our selector class pattern, but there are a couple + // of exceptions in which we overrride CSS from addons like CodeMirror and HDS, in which they use + // pascal case and a double-kebab-case. This regex checks for all of those 3 patterns and won't fail + // lint check, but will fail if its any other pattern. + 'selector-class-pattern': [ + '(^([a-z][a-z0-9]*)(-{1,2}[a-z0-9]+)*$)|(^CodeMirror)', + { + message: (selector) => + `Expected class selector "${selector}" to be kebab-case`, + }, + ], + }, +}; diff --git a/ui/admin/app/styles/_notify.scss b/ui/admin/app/styles/_notify.scss index 26ef1675a..355e7bdc7 100644 --- a/ui/admin/app/styles/_notify.scss +++ b/ui/admin/app/styles/_notify.scss @@ -1,7 +1,6 @@ // This file is essentially a rip of the default ember notify styles with some minor tweaks for positioning. -// sass-lint:disable no-vendor-prefixes -@-webkit-keyframes 'ember-notify-show' { +@-webkit-keyframes ember-notify-show { from { left: -310px; } @@ -10,7 +9,7 @@ left: 0; } } -@keyframes 'ember-notify-show' { +@keyframes ember-notify-show { from { left: -310px; } @@ -19,7 +18,7 @@ left: 0; } } -@-webkit-keyframes 'ember-notify-hide' { +@-webkit-keyframes ember-notify-hide { from { left: 0; } @@ -28,7 +27,7 @@ left: -310px; } } -@keyframes 'ember-notify-hide' { +@keyframes ember-notify-hide { from { left: 0; } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index a2a099733..868483320 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -1,4 +1,3 @@ -// sass-lint:disable no-color-literals class-name-format @use 'rose/variables/sizing'; @use 'rose/variables/media'; @use 'rose/utilities/type'; @@ -194,11 +193,11 @@ } mark { - animation: fadeBackground 0.75s linear; + animation: fade-background 0.75s linear; background-color: transparent; color: inherit; - @keyframes fadeBackground { + @keyframes fade-background { 0% { background-color: var(--action-subtler-1); } @@ -222,7 +221,7 @@ white-space: nowrap; } -// Spinning animation for refresh button icon +// Spinning animation for refresh button icon @media (prefers-reduced-motion: no-preference) { .is-loading { .rose-icon { @@ -239,7 +238,7 @@ } } -//onboarding quick setup styles +// onboarding quick setup styles .choose-path { display: block; width: sizing.rems(xl) * 15; @@ -416,7 +415,7 @@ .worker-auth-token { align-items: flex-end; display: flex; - gap: sizing.rems(m); // sass-lint:disable-line no-misspelled-properties + gap: sizing.rems(m); } .worker-success { @@ -427,7 +426,7 @@ > div { align-items: center; display: flex; - gap: sizing.rems(xs); // sass-lint:disable-line no-misspelled-properties + gap: sizing.rems(xs); p { margin-bottom: sizing.rems(xxs); @@ -484,7 +483,7 @@ &.has-icon { .flight-icon { position: absolute; - transform: translatey(sizing.rems(xl) - sizing.rems(xs)); + transform: translateY(sizing.rems(xl) - sizing.rems(xs)); } .hds-form-helper-text, @@ -577,6 +576,7 @@ left: 50%; position: absolute; top: 50%; + // centers message with editor body and gutter transform: translate(-50%, -50%); z-index: 1; @@ -587,6 +587,7 @@ border: sizing.rems(xxxxs) solid var(--ui-gray); border-radius: sizing.rems(xxxs); box-shadow: 0 sizing.rems(xxs) sizing.rems(xxs) var(--button-shadow); + // using ui-gray directly so it does not change when // user is in dark mode since the secret editor // does not change theme colors between light and dark diff --git a/ui/admin/package.json b/ui/admin/package.json index 12f60b47e..96d9cbc8d 100644 --- a/ui/admin/package.json +++ b/ui/admin/package.json @@ -21,7 +21,8 @@ "lint:hbs": "ember-template-lint .", "lint:hbs:fix": "ember-template-lint . --fix", "lint:js": "eslint .", - "lint:sass": "sass-lint -v -q", + "lint:sass": "stylelint 'app/styles/**/*.scss'", + "lint:sass:fix": "stylelint 'app/styles/**/*.scss' --fix", "format": "npm-run-all format:*", "format:hbs": "prettier --write 'app/**/*.hbs'", "format:js": "prettier --write '{app,config,stories,tests}/**/*.js' *.js", @@ -91,8 +92,9 @@ "qunit": "^2.17.2", "qunit-dom": "^2.0.0", "rose": "*", - "sass": "^1.55.0", - "sass-lint": "^1.13.1", + "stylelint": "^14.14.0", + "stylelint-config-prettier-scss": "^0.0.1", + "stylelint-config-standard-scss": "^6.0.0", "sinon": "^14.0.0", "webpack": "^5" }, diff --git a/yarn.lock b/yarn.lock index 977938cf5..77c2fbad4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7468,13 +7468,6 @@ acorn-import-assertions@^1.7.6: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9" integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw== -acorn-jsx@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" - integrity sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s= - dependencies: - acorn "^3.0.4" - acorn-jsx@^5.3.1, acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -7485,12 +7478,7 @@ acorn-walk@^7.1.1, acorn-walk@^7.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn@^3.0.4: - version "3.3.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" - integrity sha1-ReN/s56No/JbruP/U2niu18iAXo= - -acorn@^5.0.0, acorn@^5.5.0, acorn@^5.5.3: +acorn@^5.0.0, acorn@^5.5.3: version "5.7.4" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e" integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg== @@ -7615,11 +7603,6 @@ ajv-formats@^2.1.1: dependencies: ajv "^8.0.0" -ajv-keywords@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" - integrity sha1-MU3QpLM2j609/NxU7eYXG4htrzw= - ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2: version "3.5.2" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" @@ -7632,14 +7615,6 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^4.7.0: - version "4.11.8" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" - integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= - dependencies: - co "^4.6.0" - json-stable-stringify "^1.0.1" - ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" @@ -7714,11 +7689,6 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" - integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= - ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -9840,18 +9810,6 @@ call-me-maybe@^1.0.1: resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= -caller-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" - integrity sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8= - dependencies: - callsites "^0.2.0" - -callsites@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" - integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= - callsites@^3.0.0, callsites@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -10164,11 +10122,6 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: inherits "^2.0.1" safe-buffer "^5.0.1" -circular-json@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.3.tgz#815c99ea84f6809529d2f45791bdf82711352d66" - integrity sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A== - cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -10243,13 +10196,6 @@ cli-columns@^3.1.2: string-width "^2.0.0" strip-ansi "^3.0.1" -cli-cursor@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" - cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" @@ -10535,7 +10481,7 @@ commander@7.2.0: resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== -commander@^2.20.0, commander@^2.6.0, commander@^2.8.1, commander@^2.9.0: +commander@^2.20.0, commander@^2.6.0, commander@^2.9.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -10635,7 +10581,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.4.6, concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0, concat-stream@^1.6.2: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -11601,14 +11547,6 @@ doctoc@^2.2.0: underscore "^1.13.2" update-section "^0.3.3" -doctrine@^1.2.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -13729,45 +13667,6 @@ eslint-visitor-keys@^3.3.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint@^2.7.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-2.13.1.tgz#e4cc8fa0f009fb829aaae23855a29360be1f6c11" - integrity sha1-5MyPoPAJ+4KaquI4VaKTYL4fbBE= - dependencies: - chalk "^1.1.3" - concat-stream "^1.4.6" - debug "^2.1.1" - doctrine "^1.2.2" - es6-map "^0.1.3" - escope "^3.6.0" - espree "^3.1.6" - estraverse "^4.2.0" - esutils "^2.0.2" - file-entry-cache "^1.1.1" - glob "^7.0.3" - globals "^9.2.0" - ignore "^3.1.2" - imurmurhash "^0.1.4" - inquirer "^0.12.0" - is-my-json-valid "^2.10.0" - is-resolvable "^1.0.0" - js-yaml "^3.5.1" - json-stable-stringify "^1.0.0" - levn "^0.3.0" - lodash "^4.0.0" - mkdirp "^0.5.0" - optionator "^0.8.1" - path-is-absolute "^1.0.0" - path-is-inside "^1.0.1" - pluralize "^1.2.1" - progress "^1.1.8" - require-uncached "^1.0.2" - shelljs "^0.6.0" - strip-json-comments "~1.0.1" - table "^3.7.8" - text-table "~0.2.0" - user-home "^2.0.0" - eslint@^7.27.0: version "7.32.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.32.0.tgz#c6d328a14be3fb08c8d1d21e12c02fdb7a2a812d" @@ -13860,14 +13759,6 @@ esm@^3.2.25, esm@^3.2.4: resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10" integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA== -espree@^3.1.6: - version "3.5.4" - resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.4.tgz#b0f447187c8a8bed944b815a660bddf5deb5d1a7" - integrity sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A== - dependencies: - acorn "^5.5.0" - acorn-jsx "^3.0.0" - espree@^7.3.0, espree@^7.3.1: version "7.3.1" resolved "https://registry.yarnpkg.com/espree/-/espree-7.3.1.tgz#f2df330b752c6f55019f8bd89b7660039c1bbbb6" @@ -13915,7 +13806,7 @@ estraverse-fb@^1.3.2: resolved "https://registry.yarnpkg.com/estraverse-fb/-/estraverse-fb-1.3.2.tgz#d323a4cb5e5ac331cea033413a9253e1643e07c4" integrity sha1-0yOky15awzHOoDNBOpJT4WQ+B8Q= -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -14085,11 +13976,6 @@ execall@^2.0.0: dependencies: clone-regexp "^2.1.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -14425,14 +14311,6 @@ figgy-pudding@^3.4.1, figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@^1.3.5: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -14447,14 +14325,6 @@ figures@^3.0.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^1.1.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-1.3.1.tgz#44c61ea607ae4be9c1402f41f44270cbfe334ff8" - integrity sha1-RMYepgeuS+nBQC9B9EJwy/4zT/g= - dependencies: - flat-cache "^1.2.1" - object-assign "^4.0.1" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -14732,16 +14602,6 @@ fixturify@^2.1.0: matcher-collection "^2.0.1" walk-sync "^2.0.2" -flat-cache@^1.2.1: - version "1.3.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.3.4.tgz#2c2ef77525cc2929007dfffa1dd314aa9c9dee6f" - integrity sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg== - dependencies: - circular-json "^0.3.1" - graceful-fs "^4.1.2" - rimraf "~2.6.2" - write "^0.2.1" - flat-cache@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" @@ -14896,13 +14756,6 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" -front-matter@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/front-matter/-/front-matter-2.1.2.tgz#f75983b9f2f413be658c93dfd7bd8ce4078f5cdb" - integrity sha1-91mDufL0E75ljJPf172M5AePXNs= - dependencies: - js-yaml "^3.4.6" - fs-extra@8.1.0, fs-extra@^8.0.0, fs-extra@^8.0.1, fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -14942,15 +14795,6 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" - integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^3.0.0" - universalify "^0.1.0" - fs-extra@^4.0.0, fs-extra@^4.0.2, fs-extra@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -15415,7 +15259,7 @@ glob@^5.0.10: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.4, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.4, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -15539,7 +15383,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globals@^9.18.0, globals@^9.2.0: +globals@^9.18.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== @@ -15641,22 +15485,6 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - -gonzales-pe-sl@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/gonzales-pe-sl/-/gonzales-pe-sl-4.2.3.tgz#6a868bc380645f141feeb042c6f97fcc71b59fe6" - integrity sha1-aoaLw4BkXxQf7rBCxvl/zHG1n+Y= - dependencies: - minimist "1.1.x" - gonzales-pe@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/gonzales-pe/-/gonzales-pe-4.3.0.tgz#fe9dec5f3c557eead09ff868c65826be54d067b3" @@ -16330,11 +16158,6 @@ ignore-walk@^3.0.1: dependencies: minimatch "^3.0.4" -ignore@^3.1.2: - version "3.3.10" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" - integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== - ignore@^4.0.3, ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -16510,25 +16333,6 @@ inquirer@6.5.2, inquirer@^6: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-0.12.0.tgz#1ef2bfd63504df0bc75785fff8c2c41df12f077e" - integrity sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34= - dependencies: - ansi-escapes "^1.1.0" - ansi-regex "^2.0.0" - chalk "^1.0.0" - cli-cursor "^1.0.1" - cli-width "^2.0.0" - figures "^1.3.5" - lodash "^4.3.0" - readline2 "^1.0.1" - run-async "^0.1.0" - rx-lite "^3.1.2" - string-width "^1.0.1" - strip-ansi "^3.0.0" - through "^2.3.6" - inquirer@^7.3.3: version "7.3.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -16923,7 +16727,7 @@ is-my-ip-valid@^1.0.0: resolved "https://registry.yarnpkg.com/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz#7b351b8e8edd4d3995d4d066680e664d94696824" integrity sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ== -is-my-json-valid@^2.10.0, is-my-json-valid@^2.12.4: +is-my-json-valid@^2.12.4: version "2.20.5" resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz#5eca6a8232a687f68869b7361be1612e7512e5df" integrity sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A== @@ -17057,11 +16861,6 @@ is-regexp@^2.0.0: resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d" integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA== -is-resolvable@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" - integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== - is-root@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" @@ -17875,7 +17674,7 @@ js-yaml@4.0.0: dependencies: argparse "^2.0.1" -js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.2.5, js-yaml@^3.2.7, js-yaml@^3.4.6, js-yaml@^3.5.1, js-yaml@^3.5.4: +js-yaml@^3.13.1, js-yaml@^3.14.0, js-yaml@^3.2.5, js-yaml@^3.2.7: version "3.14.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== @@ -18075,13 +17874,6 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" - integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -18199,11 +17991,6 @@ known-css-properties@^0.25.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.25.0.tgz#6ebc4d4b412f602e5cfbeb4086bd544e34c0a776" integrity sha512-b0/9J1O9Jcyik1GC6KC42hJ41jKwdO/Mq8Mdo5sYN+IuRTXs2YFHZC3kZSx6ueusqa95x3wLYe/ytKjbAfGixA== -known-css-properties@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" - integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== - language-subtag-registry@~0.3.2: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -18260,14 +18047,6 @@ leven@^3.1.0: resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A== -levn@^0.3.0, levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - levn@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade" @@ -18276,6 +18055,14 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + libcipm@^4.0.8: version "4.0.8" resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-4.0.8.tgz#dcea4919e10dfbce420327e63901613b9141bc89" @@ -18718,11 +18505,6 @@ lodash.camelcase@^4.3.0: resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= -lodash.capitalize@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/lodash.capitalize/-/lodash.capitalize-4.2.1.tgz#f826c9b4e2a8511d84e3aca29db05e1a4f3b72a9" - integrity sha1-+CbJtOKoUR2E46yinbBeGk87cqk= - lodash.castarray@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" @@ -18838,7 +18620,7 @@ lodash.isplainobject@^4.0.6: resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= -lodash.kebabcase@^4.0.0, lodash.kebabcase@^4.1.1: +lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= @@ -18947,7 +18729,7 @@ lodash.without@~4.4.0: resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" integrity sha512-M3MefBwfDhgKgINVuBJCO1YR3+gf6s9HNJsIiZ/Ru77Ws6uTb9eBuvrkpzO+9iLoAaRodGuq7tyrPCx+74QYGQ== -lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.7.0, lodash@~4.17.10: +lodash@^4.17.10, lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -19545,7 +19327,7 @@ merge2@^1.2.3, merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -merge@^1.2.0, merge@^2.1.0, merge@^2.1.1: +merge@^2.1.0, merge@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/merge/-/merge-2.1.1.tgz#59ef4bf7e0b3e879186436e8481c06a6c162ca98" integrity sha512-jz+Cfrg9GWOZbQAnDQ4hlVnQky+341Yk5ru8bZSe6sIDTCIg8n9i/u7hSQGSVOF3C7lH6mGtqjkiT9G4wFLL0w== @@ -19770,7 +19552,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4, minimatch@~3.0.2: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -19800,7 +19582,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@1.1.x, minimist@1.2.5, minimist@^0.2.1, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: +minimist@1.2.5, minimist@^0.2.1, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -20051,11 +19833,6 @@ mustache@^4.2.0: resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.2.0.tgz#e5892324d60a12ec9c2a73359edca52972bf6f64" integrity sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ== -mute-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.5.tgz#8fbfabb0a98a253d3184331f9e8deb7372fac6c0" - integrity sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA= - mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -20869,11 +20646,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0, once@~1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -21623,11 +21395,6 @@ plist@^3.0.0, plist@^3.0.1, plist@^3.0.4, plist@^3.0.5: base64-js "^1.5.1" xmlbuilder "^15.1.1" -pluralize@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" - integrity sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU= - pngjs@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" @@ -21996,11 +21763,6 @@ progress-stream@^1.1.0: speedometer "~0.1.2" through2 "~0.2.3" -progress@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" - integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= - progress@^2.0.0, progress@^2.0.1, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" @@ -22751,15 +22513,6 @@ readdirp@~3.6.0: dependencies: picomatch "^2.2.1" -readline2@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" - integrity sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - mute-stream "0.0.5" - recast@^0.18.1: version "0.18.10" resolved "https://registry.yarnpkg.com/recast/-/recast-0.18.10.tgz#605ebbe621511eb89b6356a7e224bff66ed91478" @@ -23204,14 +22957,6 @@ require-relative@^0.8.7: resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" integrity sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4= -require-uncached@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" - integrity sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM= - dependencies: - caller-path "^0.1.0" - resolve-from "^1.0.0" - requireindex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" @@ -23252,11 +22997,6 @@ resolve-dir@^1.0.0, resolve-dir@^1.0.1: expand-tilde "^2.0.0" global-modules "^1.0.0" -resolve-from@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" - integrity sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY= - resolve-from@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" @@ -23361,14 +23101,6 @@ responselike@^2.0.0: dependencies: lowercase-keys "^2.0.0" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -23502,13 +23234,6 @@ rsvp@~3.2.1: resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.2.1.tgz#07cb4a5df25add9e826ebc67dcc9fd89db27d84a" integrity sha1-B8tKXfJa3Z6Cbrxn3Mn9idsn2Eo= -run-async@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/run-async/-/run-async-0.1.0.tgz#c8ad4a5e110661e402a7d21b530e009f25f8e389" - integrity sha1-yK1KXhEGYeQCp9IbUw4AnyX444k= - dependencies: - once "^1.3.0" - run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -23528,11 +23253,6 @@ run-queue@^1.0.0, run-queue@^1.0.3: dependencies: aproba "^1.1.1" -rx-lite@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-3.1.2.tgz#19ce502ca572665f3b647b10939f97fd1615f102" - integrity sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI= - rxjs@^6.4.0, rxjs@^6.6.0: version "6.6.7" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -23614,26 +23334,6 @@ sane@^5.0.1: minimist "^1.1.1" walker "~1.0.5" -sass-lint@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sass-lint/-/sass-lint-1.13.1.tgz#5fd2b2792e9215272335eb0f0dc607f61e8acc8f" - integrity sha512-DSyah8/MyjzW2BWYmQWekYEKir44BpLqrCFsgs9iaWiVTcwZfwXHF586hh3D1n+/9ihUNMfd8iHAyb9KkGgs7Q== - dependencies: - commander "^2.8.1" - eslint "^2.7.0" - front-matter "2.1.2" - fs-extra "^3.0.1" - glob "^7.0.0" - globule "^1.0.0" - gonzales-pe-sl "^4.2.3" - js-yaml "^3.5.4" - known-css-properties "^0.3.0" - lodash.capitalize "^4.1.0" - lodash.kebabcase "^4.0.0" - merge "^1.2.0" - path-is-absolute "^1.0.0" - util "^0.10.3" - sass@^1.43.4: version "1.56.0" resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.0.tgz#134032075a3223c8d49cb5c35e091e5ba1de8e0a" @@ -23916,7 +23616,7 @@ shell-quote@1.7.2, shell-quote@^1.6.1, shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.7.4.tgz#33fe15dee71ab2a81fcbd3a52106c5cfb9fb75d8" integrity sha512-8o/QEhSSRb1a5i7TFR0iM4G16Z0vYB2OQVs4G3aAFXjn3T6yEx8AZxy1PgDF7I00LZHYA3WxaSYIf5e5sAX8Rw== -shelljs@^0.6.0, shelljs@^0.8.4, shelljs@^0.8.5: +shelljs@^0.8.4, shelljs@^0.8.5: version "0.8.5" resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== @@ -24005,11 +23705,6 @@ slash@^4.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-4.0.0.tgz#2422372176c4c6c5addb5e2ada885af984b396a7" integrity sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew== -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= - slice-ansi@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" @@ -24792,11 +24487,6 @@ strip-json-comments@3.1.1, strip-json-comments@^3.0.1, strip-json-comments@^3.1. resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -strip-json-comments@~1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-1.0.4.tgz#1e15fbcac97d3ee99bf2d73b4c656b082bbafb91" - integrity sha1-HhX7ysl9Pumb8tc7TGVrCCu6+5E= - strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" @@ -25177,18 +24867,6 @@ tabbable@^5.2.1: resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== -table@^3.7.8: - version "3.8.3" - resolved "https://registry.yarnpkg.com/table/-/table-3.8.3.tgz#2bbc542f0fda9861a755d3947fefd8b3f513855f" - integrity sha1-K7xULw/amGGnVdOUf+/Ys/UThV8= - dependencies: - ajv "^4.7.0" - ajv-keywords "^1.0.0" - chalk "^1.1.1" - lodash "^4.0.0" - slice-ansi "0.0.4" - string-width "^2.0.0" - table@^6.0.9, table@^6.6.0: version "6.7.1" resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" @@ -26220,13 +25898,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" - integrity sha1-nHC/2Babwdy/SGBODwS4tJzenp8= - dependencies: - os-homedir "^1.0.0" - username-sync@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/username-sync/-/username-sync-1.0.3.tgz#ae41c5c8a4c8c2ecc1443a7d0742742bd7e36732" @@ -26282,13 +25953,6 @@ util@0.10.3: dependencies: inherits "2.0.1" -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" - integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A== - dependencies: - inherits "2.0.3" - util@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" @@ -26957,13 +26621,6 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" -write@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" - integrity sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c= - dependencies: - mkdirp "^0.5.1" - ws@^6.1.0, ws@^7.4.6, ws@~7.4.2: version "7.5.3" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.3.tgz#160835b63c7d97bfab418fc1b8a9fced2ac01a74" From f6cad6a9ee9443a20c2d4991c277c8ded5f174c5 Mon Sep 17 00:00:00 2001 From: Carlos Alcaide Corvo Date: Wed, 14 Dec 2022 10:28:54 -0800 Subject: [PATCH 11/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Upgrade=20ember-f?= =?UTF-8?q?ocus-trap=20(#1530)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upgrade ember-focus-trap (need for Ember 4 work) --- addons/rose/package.json | 2 +- yarn.lock | 88 ++++++++++++++++++++-------------------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/addons/rose/package.json b/addons/rose/package.json index b96762f51..343e908f0 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -42,7 +42,7 @@ "ember-cli-htmlbars": "^6.1.0", "ember-cli-sass": "^11.0.1", "ember-composable-helpers": "^5.0.0", - "ember-focus-trap": "^0.7.0", + "ember-focus-trap": "^1.0.1", "ember-named-blocks-polyfill": "^0.2.5", "ember-radio-button": "github:yapplabs/ember-radio-button.git#b2ab65f", "ember-uuid": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 77c2fbad4..573d3c886 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3907,6 +3907,15 @@ ember-cli-version-checker "^5.1.2" semver "^7.3.5" +"@embroider/addon-shim@^1.0.0": + version "1.8.4" + resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.4.tgz#0e7f32c5506bf0f3eb0840506e31c36c7053763c" + integrity sha512-sFhfWC0vI18KxVenmswQ/ShIvBg4juL8ubI+Q3NTSdkCTeaPQ/DIOUF6oR5DCQ8eO/TkIaw+kdG3FkTY6yNJqA== + dependencies: + "@embroider/shared-internals" "^2.0.0" + broccoli-funnel "^3.0.8" + semver "^7.3.8" + "@embroider/addon-shim@^1.3.0", "@embroider/addon-shim@^1.5.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@embroider/addon-shim/-/addon-shim-1.8.3.tgz#2368510b8ce42d50d02cb3289c32e260dfa34bd9" @@ -3995,18 +4004,6 @@ resolve "^1.8.1" semver "^7.3.2" -"@embroider/macros@^0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@embroider/macros/-/macros-0.41.0.tgz#3e78b6f388d7229906abf4c75edfff8bb0208aca" - integrity sha512-QISzwEEfLsskZeL0jyZDs1RoQSotwBWj+4upTogNHuxQP5j/9H3IMG/3QB1gh8GEpbudATb/cS4NDYK3UBxufw== - dependencies: - "@embroider/shared-internals" "0.41.0" - assert-never "^1.1.0" - ember-cli-babel "^7.23.0" - lodash "^4.17.10" - resolve "^1.8.1" - semver "^7.3.2" - "@embroider/shared-internals@0.40.0": version "0.40.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-0.40.0.tgz#2f768c60f4f35ba5f9228f046f70324851e8bfe2" @@ -4020,19 +4017,6 @@ semver "^7.3.2" typescript-memoize "^1.0.0-alpha.3" -"@embroider/shared-internals@0.41.0": - version "0.41.0" - resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-0.41.0.tgz#2553f026d4f48ea1fd11235501feb63bf49fa306" - integrity sha512-fiqUVB6cfh2UBEFE4yhT5EzagkZ1Q26+OhBV0nJszFEJZx4DqVIb3pxSSZ8P+HhpxuJsQ2XpMA/j02ZPFZfbdQ== - dependencies: - ember-rfc176-data "^0.3.17" - fs-extra "^7.0.1" - lodash "^4.17.10" - pkg-up "^3.1.0" - resolve-package-path "^1.2.2" - semver "^7.3.2" - typescript-memoize "^1.0.0-alpha.3" - "@embroider/shared-internals@1.8.0": version "1.8.0" resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-1.8.0.tgz#4c9b563e5f155410d982a30667b027a227be7312" @@ -4061,6 +4045,20 @@ semver "^7.3.5" typescript-memoize "^1.0.1" +"@embroider/shared-internals@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@embroider/shared-internals/-/shared-internals-2.0.0.tgz#d8205ec6944362aeecfbb51143db352430ced316" + integrity sha512-qZ2/xky9mWm5YC6noOa6AiAwgISEQ78YTZNv4SNu2PFgEK/H+Ha/3ddngzGSsnXkVnIHZyxIBzhxETonQYHY9g== + dependencies: + babel-import-util "^1.1.0" + ember-rfc176-data "^0.3.17" + fs-extra "^9.1.0" + js-string-escape "^1.0.1" + lodash "^4.17.21" + resolve-package-path "^4.0.1" + semver "^7.3.5" + typescript-memoize "^1.0.1" + "@embroider/test-setup@^0.48.1": version "0.48.1" resolved "https://registry.yarnpkg.com/@embroider/test-setup/-/test-setup-0.48.1.tgz#f64da84f8241433d0ba92154fa1eb3137ecd4df8" @@ -12770,16 +12768,13 @@ ember-fetch@^8.1.1: node-fetch "^2.6.1" whatwg-fetch "^3.6.2" -ember-focus-trap@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/ember-focus-trap/-/ember-focus-trap-0.7.0.tgz#8c3fa66a0d393dad2994db82e2fa227103875fc0" - integrity sha512-WHOD8jTcCzsRb0cU8J5SKObrxbdD8rPSWrSUjZ2QYu9dVbaXg6/hZxcN5JrmPY1ArnsRaLMPdOUALYeZTP29og== +ember-focus-trap@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ember-focus-trap/-/ember-focus-trap-1.0.1.tgz#a99565f6ce55d500b92a0965e79e3ad04219f157" + integrity sha512-ZUyq5ZkIuXp+ng9rCMkqBh36/V95PltL7iljStkma4+651xlAy3Z84L9WOu/uOJyVpNUxii8RJBbAySHV6c+RQ== dependencies: - "@embroider/macros" "^0.41.0" - ember-auto-import "^1.11.2" - ember-cli-babel "^7.26.3" - ember-modifier-manager-polyfill "^1.2.0" - focus-trap "^6.4.0" + "@embroider/addon-shim" "^1.0.0" + focus-trap "^6.7.1" "ember-get-config@^0.2.4 || ^0.3.0": version "0.3.0" @@ -14641,12 +14636,12 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -focus-trap@^6.4.0: - version "6.6.1" - resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.6.1.tgz#761ce2c82ddd72beeb049e968bc8414e25b704aa" - integrity sha512-x9BWuAeF5UrfWuYKJ3jYrjcVYSYptS9CqtxH5IH7lPlZrMsaugKeAa0HtoZSBZe5DmeTMx2m0qY464ZMzqarzw== +focus-trap@^6.7.1: + version "6.9.4" + resolved "https://registry.yarnpkg.com/focus-trap/-/focus-trap-6.9.4.tgz#436da1a1d935c48b97da63cd8f361c6f3aa16444" + integrity sha512-v2NTsZe2FF59Y+sDykKY+XjqZ0cPfhq/hikWVL88BqLivnNiEffAsac6rP6H45ff9wG9LL5ToiDqrLEP9GX9mw== dependencies: - tabbable "^5.2.1" + tabbable "^5.3.3" follow-redirects@^1.0.0, follow-redirects@^1.14.8: version "1.15.2" @@ -23464,6 +23459,13 @@ semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5: dependencies: lru-cache "^6.0.0" +semver@^7.3.8: + version "7.3.8" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" + integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + dependencies: + lru-cache "^6.0.0" + send@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" @@ -24862,10 +24864,10 @@ synchronous-promise@^2.0.15: resolved "https://registry.yarnpkg.com/synchronous-promise/-/synchronous-promise-2.0.16.tgz#669b75e86b4295fdcc1bb0498de9ac1af6fd51a9" integrity sha512-qImOD23aDfnIDNqlG1NOehdB9IYsn1V9oByPjKY1nakv2MQYCEMyX033/q+aEtYCpmYK1cv2+NTmlH+ra6GA5A== -tabbable@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.2.1.tgz#e3fda7367ddbb172dcda9f871c0fdb36d1c4cd9c" - integrity sha512-40pEZ2mhjaZzK0BnI+QGNjJO8UYx9pP5v7BGe17SORTO0OEuuaAwQTkAp8whcZvqon44wKFOikD+Al11K3JICQ== +tabbable@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-5.3.3.tgz#aac0ff88c73b22d6c3c5a50b1586310006b47fbf" + integrity sha512-QD9qKY3StfbZqWOPLp0++pOrAVb/HbUi5xCc8cUo4XjP19808oaMiDzn0leBY5mCespIBM0CIZePzZjgzR83kA== table@^6.0.9, table@^6.6.0: version "6.7.1" From c933aa88403362e986729fd1b7f1e5aa449011e9 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Wed, 14 Dec 2022 13:36:30 -0600 Subject: [PATCH 12/25] =?UTF-8?q?test:=20=F0=9F=92=8D=20Add=20delete=20tes?= =?UTF-8?q?ts=20for=20JSON=20Credentials=20(#1526)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 Add delete tests for JSON Credentials ✅ Closes: ICU-6806 * refactor: 💡 update JSON variable names --- .../credentials/delete-test.js | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js index 57192d5f0..c5474d917 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js @@ -13,6 +13,7 @@ module( let getUsernamePasswordCredentialCount; let getUsernameKeyPairCredentialCount; + let getJSONCredentialCount; const instances = { scopes: { @@ -28,6 +29,7 @@ module( credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; hooks.beforeEach(function () { @@ -54,6 +56,11 @@ module( credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -61,6 +68,7 @@ module( urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; // Generate resource counter getUsernamePasswordCredentialCount = () => { return this.server.schema.credentials.where({ @@ -71,6 +79,10 @@ module( return this.server.schema.credentials.where({ type: 'ssh_private_key' }) .length; }; + getJSONCredentialCount = () => { + return this.server.schema.credentials.where({ type: 'json' }) + .length; + }; authenticateSession({}); }); @@ -100,6 +112,19 @@ module( ); }); + test('can delete JSON credential', async function (assert) { + assert.expect(2); + const jsonCredentialCount = + getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual( + getJSONCredentialCount(), + jsonCredentialCount - 1 + ); + }); + test('cannot delete a username & password credential without proper authorization', async function (assert) { assert.expect(3); const usernamePasswordCredentialCount = @@ -138,6 +163,25 @@ module( ); }); + test('cannot delete a JSON credential without proper authorization', async function (assert) { + assert.expect(3); + const jsonCredentialCount = + getJSONCredentialCount(); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item !== 'delete' + ); + await visit(urls.jsonCredential); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert + .dom('.rose-layout-page-actions .rose-dropdown-button-danger') + .doesNotExist(); + assert.strictEqual( + getJSONCredentialCount(), + jsonCredentialCount + ); + }); + test('can accept delete username & password credential via dialog', async function (assert) { assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); @@ -170,6 +214,22 @@ module( ); }); + test('can accept delete JSON credential via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const jsonCredentialCount = + getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await click('.rose-dialog footer .rose-button-primary'); + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual( + getJSONCredentialCount(), + jsonCredentialCount - 1 + ); + }); + test('can cancel delete username & password credential via dialog', async function (assert) { assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); @@ -202,6 +262,22 @@ module( ); }); + test('can cancel delete JSON credential via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const jsonCredentialCount = + getJSONCredentialCount(); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await click('.rose-dialog footer .rose-button-secondary'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual( + getJSONCredentialCount(), + jsonCredentialCount + ); + }); + test('deleting a username & password credential which errors displays error message', async function (assert) { assert.expect(1); this.server.del('/credentials/:id', () => { @@ -237,5 +313,23 @@ module( await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.'); }); + + test('deleting a JSON credential which errors displays error message', async function (assert) { + assert.expect(1); + this.server.del('/credentials/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.jsonCredential); + await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + assert.ok(find('[role="alert"]').textContent.trim(), 'Oops.'); + }); } ); From ed9e67482363fb96d8f8928881a59d793ff6f543 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Wed, 14 Dec 2022 18:55:11 -0500 Subject: [PATCH 13/25] Refactor info field component (#1527) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 refactor info-field component Overall context of how the `` was changed to the correct order. Corrected css grid to have the order of `label,input-field,helper-text`. Also refactored all component instances affected by the change. * refactor: 💡 refactor styling for icon to be center refactor will get the icon as close to center as possible. * chore: 🤖 tweak info field styles Co-authored-by: dvonanderson --- ui/admin/app/styles/app.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 868483320..849259a2c 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -468,6 +468,7 @@ .hds-form-text-input { background-color: transparent; border: 0; + color: var(--token-form-label-color); grid-area: text-input; font-size: sizing.rems(m); padding: 0; @@ -475,7 +476,6 @@ .hds-form-helper-text { color: var(--token-form-label-color); - line-height: sizing.rems(m) + sizing.rems(xxs); grid-area: helper-text; } } From 92db4d4947b2ac8c3f3a4fcaa67c9ec98b60f12f Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Thu, 15 Dec 2022 09:11:01 -0800 Subject: [PATCH 14/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20remove=20rose=20r?= =?UTF-8?q?adiocard=20and=20styles=20(#1521)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: 🤖 remove rose radio card and styles --- .../components/rose/form/radio/card/index.js | 25 -- .../rose/form/radio/card/index.stories.mdx | 86 +------ .../rose/form/radio/group/index.hbs | 8 - .../rose/form/radio/group/index.stories.mdx | 84 +----- .../styles/rose/components/form/_index.scss | 1 - .../rose/components/form/_radio-card.scss | 240 ------------------ .../components/rose/form/radio/card-test.js | 53 ---- ui/admin/app/abilities/credential.js | 3 +- .../form/worker/create-worker-led/index.js | 4 +- .../brokered-credential-sources-test.js | 14 +- .../tests/unit/abilities/credential-test.js | 72 ++---- 11 files changed, 46 insertions(+), 544 deletions(-) delete mode 100644 addons/rose/addon/components/rose/form/radio/card/index.js delete mode 100644 addons/rose/app/styles/rose/components/form/_radio-card.scss delete mode 100644 addons/rose/tests/integration/components/rose/form/radio/card-test.js diff --git a/addons/rose/addon/components/rose/form/radio/card/index.js b/addons/rose/addon/components/rose/form/radio/card/index.js deleted file mode 100644 index f4aaab3bb..000000000 --- a/addons/rose/addon/components/rose/form/radio/card/index.js +++ /dev/null @@ -1,25 +0,0 @@ -import RoseFormRadioRadioComponent from '../radio'; -import { deprecate } from '@ember/debug'; - -export default class RoseFormRadioCardComponent extends RoseFormRadioRadioComponent { - // =attributes - layout = this.args.layout; - className = `rose-form-radio-card ${this.layout}`; - - constructor() { - super(...arguments); - deprecate( - `Rose Form/Radio/Card component is deprecated, use HDS Form/RadioCard instead. For more info, refer to `, - false, - { - id: 'rose.form.radio.card', - until: '4.8.0', - for: 'rose', - since: { - enabled: '3.28.8', - }, - url: 'https://boundary-ui-storybook.vercel.app/?path=/docs/rose-form-radio-radio-card--card', - } - ); - } -} diff --git a/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx index 5cd8de8ab..9d284938c 100644 --- a/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/card/index.stories.mdx @@ -1,15 +1,12 @@ import { Meta, Story, Preview } from '@storybook/addon-docs'; import { hbs } from 'ember-cli-htmlbars'; - + -# HDS::Form::RadioCard (Recommended) +# RadioCard (removed) -> **_NOTE:_** Replaces the deprecated Rose/Form/Radio/Card. - -Refer to the [HDS Form::RadioCard -documentation](https://design-system-components-hashicorp.vercel.app/components/form/radio-card) -for a complete overview +Rose/Form/Select has been removed in favor of +[HDS::Form::RadioCard](https://design-system-components-hashicorp.vercel.app/components/form/radio-card). ```handlebars @@ -44,78 +41,3 @@ export const HDSTemplate = (args) => ({ {HDSTemplate.bind({})} - -# Rose/Form/Radio/Card (Deprecated) - -Rose/Form/Radio/Card has been deprecated in favor of [HDS::Form::RadioCard](https://design-system-components-hashicorp.vercel.app/components/form/radio-card) - -### usage: - -# Form Radiofield in a wide format - -Renders a form radiofield in a Wide format. - -```handlebars - -``` - -Renders a form radiofield in a tile format. - -```handlebars - -``` - -export const Template = (args) => ({ - template: hbs` - `, - context: args, -}); - - - - {Template.bind({})} - - diff --git a/addons/rose/addon/components/rose/form/radio/group/index.hbs b/addons/rose/addon/components/rose/form/radio/group/index.hbs index 8e090407d..e883eb431 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.hbs +++ b/addons/rose/addon/components/rose/form/radio/group/index.hbs @@ -8,13 +8,5 @@ changed=@changed inline=@inline ) - card=(component - 'rose/form/radio/card' - name=@name - selectedValue=@selectedValue - disabled=@disabled - changed=@changed - layout=@layout - ) ) }} \ No newline at end of file diff --git a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx index a698fedda..dd0d5b106 100644 --- a/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/group/index.stories.mdx @@ -75,9 +75,9 @@ export const Template = (args) => ({ -# HDS::Form::RadioCard::Group (Recommended) +# HDS::Form::RadioCard::Group -> **_NOTE:_** Replaces the deprecated Rose/Form/Radio/Group/Card. +> **_NOTE:_** Replaces the removed Rose/Form/Radio/Group/Card. ```handlebars ({ {HDSTemplate.bind({})} - -# Rose::Form::Radio::Group::Card (Deprecated) - -```handlebars - - - - - -``` - -export const RoseTemplate = (args) => ({ - template: hbs` - - - - `, - context: args, -}); - - - - {RoseTemplate.bind({})} - - diff --git a/addons/rose/app/styles/rose/components/form/_index.scss b/addons/rose/app/styles/rose/components/form/_index.scss index b06e535f6..73137ac8d 100644 --- a/addons/rose/app/styles/rose/components/form/_index.scss +++ b/addons/rose/app/styles/rose/components/form/_index.scss @@ -6,4 +6,3 @@ @import 'select'; @import 'radio'; @import 'fieldset'; -@import 'radio-card'; diff --git a/addons/rose/app/styles/rose/components/form/_radio-card.scss b/addons/rose/app/styles/rose/components/form/_radio-card.scss deleted file mode 100644 index 0c61fb51d..000000000 --- a/addons/rose/app/styles/rose/components/form/_radio-card.scss +++ /dev/null @@ -1,240 +0,0 @@ -@use '../../variables/sizing'; -@use '../../utilities/type'; -@use '../../variables/typography'; - -$size-xxl: sizing.rems(xxl); // 48 -$size-xl: sizing.rems(xl); // 36 -$size-l: sizing.rems(l); // 24 -$size-m: sizing.rems(m); // 16 -$size-s: sizing.rems(s); // 12 -$size-xs: sizing.rems(xs); // 8 -$size-xxs: sizing.rems(xxs); // 4 -$size-xxxs: sizing.rems(xxxs); // 2 -$size-xxxxs: sizing.rems(xxxxs); // 1 - -$radio-svg: ( - 'default': - "", - 'hover': - "", - 'focus': - "", - 'checked': - "", - 'disabled': - "", - 'disabled-checked': - "", - 'error': - "", - 'error-checked': - "", -); - -.rose-form-radio-card { - $card-wide-width: $size-m * 10; - $card-tile-width: $size-m * 7.25; - $border-width: $size-xxxs * 2; - - &.wide { - width: ($size-m) * 20; - } - - &.tile { - width: ($size-m) * 15; - - .rose-icon { - --size: #{$size-xl}; - - padding: $size-xxxs; - } - - .rose-form-radio-label { - display: flex; - flex-direction: column; - align-items: center; - height: $size-m * 9; - - &::after { - left: $card-tile-width - $border-width; - } - } - } - - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - margin: $size-m $size-xs $size-m 0; - - .radio-card-overlay { - position: absolute; - height: $size-l + $size-xxs; - background-color: var(--ui-gray-subtler-5); - width: 100%; - bottom: 0; - right: 0; - border-bottom-left-radius: $size-xxxs; - border-bottom-right-radius: $size-xxxs; - } - - .rose-form-radio-label { - @include type.type(s, bold); - - width: ($size-m) * 23; - height: 100%; - display: block; - position: relative; - text-align: left; - border: $size-xxxxs solid var(--ui-gray-subtler-2); - box-shadow: 0 $size-xxs $size-xxs - rgba(var(--stark-components), var(--opacity-2)); - border-radius: $size-xxs; - padding: $size-s $size-m ($size-xl + $size-m); - box-sizing: border-box; - - .rose-form-helper-text { - @include type.type(xs, normal); - - padding-top: $size-xs; - } - - &::after { - position: absolute; - bottom: $size-xxs + $size-xxxxs; - right: $card-wide-width - $border-width; - display: block; - width: $size-m; - height: $size-m; - transform: scale(2); - pointer-events: none; - content: ' '; - background: no-repeat 50%/50% 50%; - background-image: url($data-svg-prefix + map-get($radio-svg, 'default')); - } - } - - .rose-form-radio-field { - position: absolute; - z-index: 1; - bottom: $size-xs; - opacity: 0; - - &:not(:disabled) { - &:focus { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'focus' - )); - } - } - } - - &:hover { - ~ .rose-form-radio-label { - border: $size-xxxxs solid var(--ui-gray); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'checked' - )); - } - - .radio-card-overlay { - background-color: var(--action-subtler-1h); - - .ember-application.rose-theme-light & { - background-color: var(--action-subtler-1h); - } - @media (prefers-color-scheme: dark) { - background-color: var(--action-subtler-1); - } - - .ember-application.rose-theme-dark & { - background-color: var(--action-subtler-1); - } - } - - border: $size-xxxxs solid var(--info-subtler-1); - } - } - } - - &:disabled { - ~ .rose-form-radio-label { - border: $size-xxxxs solid var(--ui-border-subtler-1); - background-color: var(--ui-gray-subtler-6); - color: var(--ui-gray-starker-1); - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'disabled-checked' - )); - } - } - } - } - } - - &.error { - .rose-form-radio-field { - &:not(:disabled, :focus) { - ~ .rose-form-radio-label { - color: var(--ui-gray); - border: $size-xxxxs solid var(--failure); - - .radio-card-overlay { - background-color: var(--failure-subtler-2); - } - - .rose-form-helper-text.error { - color: var(--ui-gray); - } - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error' - )); - } - } - - &:checked { - ~ .rose-form-radio-label { - color: var(--ui-gray); - border: $size-xxxxs solid var(--failure); - - .radio-card-overlay { - background-color: var(--failure-subtler-2); - } - - &::after { - background-image: url($data-svg-prefix + map-get( - $radio-svg, - 'error-checked' - )); - } - } - } - } - } - } -} diff --git a/addons/rose/tests/integration/components/rose/form/radio/card-test.js b/addons/rose/tests/integration/components/rose/form/radio/card-test.js deleted file mode 100644 index 5bc55dd0e..000000000 --- a/addons/rose/tests/integration/components/rose/form/radio/card-test.js +++ /dev/null @@ -1,53 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render, find } from '@ember/test-helpers'; -import hbs from 'htmlbars-inline-precompile'; - -module('Integration | Component | rose/form/radio/card', function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - await render(hbs``); - assert.ok(await find('input')); - assert.strictEqual(await find('input').type, 'radio'); - }); - - test('it renders in a tile format when tile layout is applied', async function (assert) { - await render(hbs``); - assert.ok(await find('.tile')); - }); - - test('it supports an icon', async function (assert) { - await render( - hbs`` - ); - assert.ok(find('svg')); - }); - - test('it is checked when @value and @selectedValue values match', async function (assert) { - await render( - hbs`` - ); - assert.true(await find('input').checked); - }); - - test('it is not disabled by default', async function (assert) { - await render(hbs``); - assert.false(await find('input').disabled); - }); - - test('it is not in error state by default', async function (assert) { - await render(hbs``); - assert.notOk(await find('.error')); - }); - - test('it is disabled when @disabled={{true}}', async function (assert) { - await render(hbs``); - assert.true(await find('input').disabled); - }); - - test('it is in error state when @error={{true}}', async function (assert) { - await render(hbs``); - assert.ok(await find('.error')); - }); -}); diff --git a/ui/admin/app/abilities/credential.js b/ui/admin/app/abilities/credential.js index 0faae0a97..70bd961cd 100644 --- a/ui/admin/app/abilities/credential.js +++ b/ui/admin/app/abilities/credential.js @@ -6,7 +6,8 @@ export default class OverrideCredentialAbility extends CredentialAbility { @service features; get canRead() { - return (this.features.isEnabled('json-credentials') || this.model.type !== 'json') + return this.features.isEnabled('json-credentials') || + this.model.type !== 'json' ? super.canRead : false; } diff --git a/ui/admin/app/components/form/worker/create-worker-led/index.js b/ui/admin/app/components/form/worker/create-worker-led/index.js index 3b7bfdc27..3bc1a37bf 100644 --- a/ui/admin/app/components/form/worker/create-worker-led/index.js +++ b/ui/admin/app/components/form/worker/create-worker-led/index.js @@ -112,7 +112,9 @@ ${listenerText} return `wget -q https://releases.hashicorp.com/boundary-worker/0.11.2+hcp/boundary-worker_0.11.2+hcp_linux_amd64.zip ;\\ sudo apt-get update && sudo apt-get install unzip ;\\ unzip *.zip ;\\ -./boundary-worker server -config="${this.configFilePath || ''}/pki-worker.hcl"`; +./boundary-worker server -config="${ + this.configFilePath || '' + }/pki-worker.hcl"`; } return `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" ;\\ diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index 9f940eb04..8bd4eb77d 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -151,7 +151,9 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { }); await visit(urls.brokeredCredentialSources); - assert.dom('.rose-table-row:nth-child(3)').includesText(jsonCredential.name); + assert + .dom('.rose-table-row:nth-child(3)') + .includesText(jsonCredential.name); assert.dom(`[href="${urls.jsonCredential}"]`).doesNotExist(); }); @@ -324,7 +326,10 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); + assert.strictEqual( + findAll('tbody tr').length, + availableCredentialsCount + 1 + ); }); test('can remove a username & password type credential', async function (assert) { @@ -339,7 +344,10 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); + assert.strictEqual( + findAll('tbody tr').length, + availableCredentialsCount + 1 + ); }); test('cannot remove credential libraries without proper authorization', async function (assert) { diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js index 4e76ace86..4a0e3ef78 100644 --- a/ui/admin/tests/unit/abilities/credential-test.js +++ b/ui/admin/tests/unit/abilities/credential-test.js @@ -9,19 +9,13 @@ module('Unit | Abilities | credential', function (hooks) { const service = this.owner.lookup('service:can'); const credential = { authorized_actions: ['read'], - type: 'json' + type: 'json', }; - assert.ok( - service.can('read credential', credential) - ); - credential.type = 'username_password' - assert.ok( - service.can('read credential', credential) - ); - credential.type = 'ssh_private_key' - assert.ok( - service.can('read credential', credential) - ); + assert.ok(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.ok(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.ok(service.can('read credential', credential)); }); test('cannot read without proper authorized actions', function (assert) { @@ -29,19 +23,13 @@ module('Unit | Abilities | credential', function (hooks) { const service = this.owner.lookup('service:can'); const credential = { authorized_actions: [], - type: 'json' + type: 'json', }; - assert.notOk( - service.can('read credential', credential) - ); - credential.type = 'username_password' - assert.notOk( - service.can('read credential', credential) - ); - credential.type = 'ssh_private_key' - assert.notOk( - service.can('read credential', credential) - ); + assert.notOk(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.notOk(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.notOk(service.can('read credential', credential)); }); test('cannot read without proper authorized actions and json-credentials disabled', function (assert) { @@ -51,19 +39,13 @@ module('Unit | Abilities | credential', function (hooks) { featuresService.disable('json-credentials'); const credential = { authorized_actions: [], - type: 'json' + type: 'json', }; - assert.notOk( - service.can('read credential', credential) - ); - credential.type = 'username_password' - assert.notOk( - service.can('read credential', credential) - ); - credential.type = 'ssh_private_key' - assert.notOk( - service.can('read credential', credential) - ); + assert.notOk(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.notOk(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.notOk(service.can('read credential', credential)); }); test('cannot read when json-credentials is disabled', function (assert) { @@ -73,18 +55,12 @@ module('Unit | Abilities | credential', function (hooks) { featuresService.disable('json-credentials'); const credential = { authorized_actions: ['read'], - type: 'json' + type: 'json', }; - assert.notOk( - service.can('read credential', credential) - ); - credential.type = 'username_password' - assert.ok( - service.can('read credential', credential) - ); - credential.type = 'ssh_private_key' - assert.ok( - service.can('read credential', credential) - ); + assert.notOk(service.can('read credential', credential)); + credential.type = 'username_password'; + assert.ok(service.can('read credential', credential)); + credential.type = 'ssh_private_key'; + assert.ok(service.can('read credential', credential)); }); }); From d542acfc15ad6ff320f5629130b842fd89d9f098 Mon Sep 17 00:00:00 2001 From: Cameron Perera Date: Thu, 15 Dec 2022 13:24:48 -0600 Subject: [PATCH 15/25] =?UTF-8?q?test:=20=F0=9F=92=8D=20add=20tests=20for?= =?UTF-8?q?=20updating=20JSON=20credentials=20(#1531)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test: 💍 add tests for updating JSON credentials ✅ Closes: ICU-6805 * refactor: 💡 update click selector for discard dialog tests --- .../credentials/update-test.js | 178 +++++++++++++++++- 1 file changed, 175 insertions(+), 3 deletions(-) diff --git a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js index 9e70f5594..2e30e8702 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/update-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/update-test.js @@ -1,5 +1,12 @@ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, fillIn } from '@ember/test-helpers'; +import { + visit, + currentURL, + find, + click, + fillIn, + waitUntil, +} from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; @@ -20,6 +27,7 @@ module( staticCredentialStore: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; const urls = { @@ -29,6 +37,7 @@ module( credentials: null, usernamePasswordCredential: null, usernameKeyPairCredential: null, + jsonCredential: null, }; hooks.beforeEach(function () { @@ -56,6 +65,11 @@ module( credentialStore: instances.staticCredentialStore, type: 'ssh_private_key', }); + instances.jsonCredential = this.server.create('credential', { + scope: instances.scopes.project, + credentialStore: instances.staticCredentialStore, + type: 'json', + }); // Generate route URLs for resources urls.projectScope = `/scopes/${instances.scopes.project.id}`; urls.credentialStores = `${urls.projectScope}/credential-stores`; @@ -63,6 +77,7 @@ module( urls.credentials = `${urls.staticCredentialStore}/credentials`; urls.usernamePasswordCredential = `${urls.credentials}/${instances.usernamePasswordCredential.id}`; urls.usernameKeyPairCredential = `${urls.credentials}/${instances.usernameKeyPairCredential.id}`; + urls.jsonCredential = `${urls.credentials}/${instances.jsonCredential.id}`; authenticateSession({}); }); @@ -98,6 +113,21 @@ module( ); }); + test('can save changes to existing JSON credential', async function (assert) { + assert.expect(3); + const mockInput = 'random string'; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('.rose-form-actions [type="submit"]'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + mockInput + ); + }); + test('cannot make changes to an existing username & password credential without proper authorization', async function (assert) { assert.expect(1); instances.usernamePasswordCredential.authorized_actions = @@ -122,6 +152,18 @@ module( .doesNotExist(); }); + test('cannot make changes to an existing JSON credential without proper authorization', async function (assert) { + assert.expect(1); + instances.jsonCredential.authorized_actions = + instances.jsonCredential.authorized_actions.filter( + (item) => item !== 'update' + ); + await visit(urls.jsonCredential); + assert + .dom('.rose-layout-page-actions .rose-button-secondary') + .doesNotExist(); + }); + test('can cancel changes to existing username & password credential', async function (assert) { assert.expect(2); const mockInput = 'random string'; @@ -150,6 +192,20 @@ module( ); }); + test('can cancel changes to existing JSON credential', async function (assert) { + assert.expect(2); + const mockInput = 'random string'; + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('.rose-form-actions [type="button"]'); + assert.notEqual(instances.jsonCredential.name, mockInput); + assert.strictEqual( + find('[name="name"]').value, + instances.jsonCredential.name + ); + }); + test('saving an existing username & password credential with invalid fields displays error message', async function (assert) { assert.expect(2); const mockInput = 'random string'; @@ -222,6 +278,42 @@ module( ); }); + test('saving an existing JSON credential with invalid fields displays error message', async function (assert) { + assert.expect(2); + const mockInput = 'random string'; + this.server.patch('/credentials/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'Error in provided request.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + await click('[type="submit"]'); + assert.ok( + find('[role="alert"]').textContent.trim(), + 'Error in provided request.' + ); + assert.ok( + find('.rose-form-error-message').textContent.trim(), + 'Name is required.' + ); + }); + test('can discard unsaved username & password credential changes via dialog', async function (assert) { assert.expect(5); const mockInput = 'random string'; @@ -236,7 +328,10 @@ module( await visit(urls.credentials); } catch (e) { assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'username_password' }) @@ -260,7 +355,10 @@ module( await visit(urls.credentials); } catch (e) { assert.dom('.rose-dialog').isVisible(); - await click('.rose-dialog-footer button:first-child', 'Click Discard'); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); assert.strictEqual(currentURL(), urls.credentials); assert.notEqual( this.server.schema.credentials.where({ type: 'ssh_private_key' }) @@ -270,6 +368,32 @@ module( } }); + test('can discard unsaved JSON credential changes via dialog', async function (assert) { + assert.expect(5); + const mockInput = 'random string'; + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + await fillIn('[name="name"]', mockInput); + assert.strictEqual(currentURL(), urls.jsonCredential); + try { + await visit(urls.credentials); + } catch (e) { + assert.dom('.rose-dialog').isVisible(); + await click( + '.rose-dialog-footer button:first-of-type', + 'Click Discard' + ); + assert.strictEqual(currentURL(), urls.credentials); + assert.notEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + mockInput + ); + } + }); + test('can cancel discard unsaved username & password credential changes via dialog', async function (assert) { assert.expect(6); const mockInput = 'random string'; @@ -322,6 +446,31 @@ module( } }); + test('can cancel discard unsaved JSON credential changes via dialog', async function (assert) { + assert.expect(6); + const mockInput = 'random string'; + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + assert.notEqual(instances.jsonCredential.name, mockInput); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + const credentialName = find('[name="name"]').value; + await fillIn('[name="name"]', mockInput); + assert.strictEqual(currentURL(), urls.jsonCredential); + try { + await visit(urls.credentials); + } catch (e) { + assert.dom('.rose-dialog').isVisible(); + await click('.rose-dialog-footer button:last-child', 'Click Cancel'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.strictEqual(find('[name="name"]').value, mockInput); + assert.strictEqual( + this.server.schema.credentials.where({ type: 'json' }).models[0].name, + credentialName + ); + } + }); + test('password field renders in edit mode only for a username & password credential', async function (assert) { assert.expect(3); await visit(urls.usernamePasswordCredential); @@ -341,5 +490,28 @@ module( assert.dom('[name="private_key"]').isVisible(); assert.dom('[name="private_key_passphrase"]').isVisible(); }); + + test('secret editor is in actionable state when entering edit mode of a JSON credential', async function (assert) { + assert.expect(3); + await visit(urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').doesNotExist(); + await click('form [type="button"]', 'Activate edit mode'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').isVisible(); + }); + + test('secret editor enters editing state when clicking edit button in the secret editor of a JSON credential', async function (assert) { + assert.expect(4); + await visit(urls.jsonCredential); + await click('form [type="button"]', 'Activate edit mode'); + assert.strictEqual(currentURL(), urls.jsonCredential); + assert.dom('.secret-editor-skeleton-message button').isVisible(); + await click( + '.secret-editor-skeleton-message button', + 'Enter editing mode' + ); + assert.dom('.secret-editor-skeleton-message button').doesNotExist(); + await waitUntil(() => assert.dom('.CodeMirror').isVisible()); + }); } ); From f1b7a9cd84284e1afdcedf431f416f05a4811fd5 Mon Sep 17 00:00:00 2001 From: Michele Degges Date: Fri, 16 Dec 2022 09:42:40 -0800 Subject: [PATCH 16/25] OSS->ENT merge trigger (#1520) --- .github/workflows/oss-ent-merge-trigger.yml | 25 +++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/oss-ent-merge-trigger.yml diff --git a/.github/workflows/oss-ent-merge-trigger.yml b/.github/workflows/oss-ent-merge-trigger.yml new file mode 100644 index 000000000..b8aa222d2 --- /dev/null +++ b/.github/workflows/oss-ent-merge-trigger.yml @@ -0,0 +1,25 @@ +name: Trigger OSS to Enterprise Merge +on: + pull_request_target: + types: + - closed + branches: + - main + +jobs: + trigger-oss-merge: + # Run this only on merge events in OSS repo + if: ${{ github.event.pull_request.merged && github.repository == 'hashicorp/boundary-ui' }} + runs-on: ubuntu-latest + steps: + - name: Trigger Merge + env: + GIT_REF: ${{ github.ref_name }} + GIT_SHA: ${{ github.sha }} + GH_PAT: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + GIT_ACTOR: ${{ github.actor }} + run: | + curl -H "Authorization: token $GH_PAT" \ + -H 'Accept: application/json' \ + -d "{\"event_type\": \"oss-merge\", \"client_payload\": {\"git-ref\": \"${GIT_REF}\", \"git-sha\": \"${GIT_SHA}\", \"git-actor\": \"${GIT_ACTOR}\" }}" \ + "https://api.github.com/repos/hashicorp/boundary-ui-enterprise/dispatches" \ No newline at end of file From 51c1aba794e6c7d078c15e8dbb4bed8ea1c13485 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 16 Dec 2022 15:06:19 -0500 Subject: [PATCH 17/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20cleanup=20json=20?= =?UTF-8?q?creds=20code=20/=20tests=20(#1535)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/models/credential.js | 8 +++ addons/api/addon/serializers/credential.js | 4 +- .../api/tests/unit/models/credential-test.js | 16 +++++ ui/admin/app/abilities/credential.js | 8 ++- .../form/field/json-secret/index.js | 2 +- .../form/worker/create-worker-led/index.js | 21 ++++--- .../credentials/delete-test.js | 35 +++-------- .../form/field/json-secret-skeleton-test.js | 21 ++++--- .../components/form/field/json-secret-test.js | 20 ++++-- .../tests/unit/abilities/credential-test.js | 63 +++++++++++-------- 10 files changed, 117 insertions(+), 81 deletions(-) diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index eb5474206..a280ae071 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -18,6 +18,14 @@ export default class CredentialModel extends GeneratedCredentialModel { return this.id.startsWith('cred'); } + /** + * True if type is `json`. + * @type {boolean} + */ + get isJSON() { + return this.type === 'json'; + } + /** * True if credential is an unknown type. * @type {boolean} diff --git a/addons/api/addon/serializers/credential.js b/addons/api/addon/serializers/credential.js index 5c61d93dc..78878b38c 100644 --- a/addons/api/addon/serializers/credential.js +++ b/addons/api/addon/serializers/credential.js @@ -20,7 +20,9 @@ export default class CredentialSerializer extends ApplicationSerializer { delete serialized.attributes.username; if (serialized?.attributes?.json_object) { - serialized.attributes.object = JSON.parse(serialized.attributes.json_object); + serialized.attributes.object = JSON.parse( + serialized.attributes.json_object + ); delete serialized.attributes.json_object; } } diff --git a/addons/api/tests/unit/models/credential-test.js b/addons/api/tests/unit/models/credential-test.js index 21b5b98ce..a0f0497e2 100644 --- a/addons/api/tests/unit/models/credential-test.js +++ b/addons/api/tests/unit/models/credential-test.js @@ -22,6 +22,22 @@ module('Unit | Model | credential', function (hooks) { assert.false(modelC.isStatic); }); + test('it has isJSON property and returns the expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelA = store.createRecord('credential', { type: 'json' }); + const modelB = store.createRecord('credential', { + type: 'username_password', + }); + const modelC = store.createRecord('credential', { + type: 'ssh_private_key', + }); + + assert.true(modelA.isJSON); + assert.false(modelB.isJSON); + assert.false(modelC.isJSON); + }); + test('it has isUnknown property and returns the expected values', async function (assert) { assert.expect(3); const store = this.owner.lookup('service:store'); diff --git a/ui/admin/app/abilities/credential.js b/ui/admin/app/abilities/credential.js index 70bd961cd..825341313 100644 --- a/ui/admin/app/abilities/credential.js +++ b/ui/admin/app/abilities/credential.js @@ -5,9 +5,13 @@ export default class OverrideCredentialAbility extends CredentialAbility { // =service @service features; + /** + * This override ensures that JSON credentials may be read only if the + * json-credentials feature flag is enabled. All other types are subject + * to the standard logic found in the api addon. + */ get canRead() { - return this.features.isEnabled('json-credentials') || - this.model.type !== 'json' + return this.features.isEnabled('json-credentials') || !this.model.isJSON ? super.canRead : false; } diff --git a/ui/admin/app/components/form/field/json-secret/index.js b/ui/admin/app/components/form/field/json-secret/index.js index d70539cdb..d1e26ec09 100644 --- a/ui/admin/app/components/form/field/json-secret/index.js +++ b/ui/admin/app/components/form/field/json-secret/index.js @@ -41,4 +41,4 @@ export default class FormFieldJsonSecretComponent extends Component { enableEditing() { if (!this.args.disabled) this.editing = true; } -} \ No newline at end of file +} diff --git a/ui/admin/app/components/form/worker/create-worker-led/index.js b/ui/admin/app/components/form/worker/create-worker-led/index.js index 3bc1a37bf..24afe932f 100644 --- a/ui/admin/app/components/form/worker/create-worker-led/index.js +++ b/ui/admin/app/components/form/worker/create-worker-led/index.js @@ -108,18 +108,19 @@ ${listenerText} * @type {string} */ get installBoundaryText() { - if (this.features.isEnabled('byow-pki-hcp-cluster-id')) { - return `wget -q https://releases.hashicorp.com/boundary-worker/0.11.2+hcp/boundary-worker_0.11.2+hcp_linux_amd64.zip ;\\ -sudo apt-get update && sudo apt-get install unzip ;\\ -unzip *.zip ;\\ -./boundary-worker server -config="${ - this.configFilePath || '' - }/pki-worker.hcl"`; - } - return `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ + const configPath = this.configFilePath || ''; + const ossContent = `curl -fsSL https://apt.releases.hashicorp.com/gpg | sudo apt-key add - ;\\ sudo apt-add-repository "deb [arch=amd64] https://apt.releases.hashicorp.com $(lsb_release -cs) main" ;\\ sudo apt-get update && sudo apt-get install boundary ;\\ -boundary server -config="${this.configFilePath || ''}/pki-worker.hcl"`; +boundary server -config="${configPath}/pki-worker.hcl"`; + const hcpContent = `wget -q https://releases.hashicorp.com/boundary-worker/0.11.2+hcp/boundary-worker_0.11.2+hcp_linux_amd64.zip ;\\ +sudo apt-get update && sudo apt-get install unzip ;\\ +unzip *.zip ;\\ +./boundary-worker server -config="${configPath}/pki-worker.hcl"`; + + return this.features.isEnabled('byow-pki-hcp-cluster-id') + ? hcpContent + : ossContent; } /** diff --git a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js index c5474d917..108cc7db1 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/delete-test.js @@ -80,8 +80,7 @@ module( .length; }; getJSONCredentialCount = () => { - return this.server.schema.credentials.where({ type: 'json' }) - .length; + return this.server.schema.credentials.where({ type: 'json' }).length; }; authenticateSession({}); }); @@ -114,15 +113,11 @@ module( test('can delete JSON credential', async function (assert) { assert.expect(2); - const jsonCredentialCount = - getJSONCredentialCount(); + const jsonCredentialCount = getJSONCredentialCount(); await visit(urls.jsonCredential); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); assert.strictEqual(currentURL(), urls.credentials); - assert.strictEqual( - getJSONCredentialCount(), - jsonCredentialCount - 1 - ); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount - 1); }); test('cannot delete a username & password credential without proper authorization', async function (assert) { @@ -165,8 +160,7 @@ module( test('cannot delete a JSON credential without proper authorization', async function (assert) { assert.expect(3); - const jsonCredentialCount = - getJSONCredentialCount(); + const jsonCredentialCount = getJSONCredentialCount(); instances.jsonCredential.authorized_actions = instances.jsonCredential.authorized_actions.filter( (item) => item !== 'delete' @@ -176,10 +170,7 @@ module( assert .dom('.rose-layout-page-actions .rose-dropdown-button-danger') .doesNotExist(); - assert.strictEqual( - getJSONCredentialCount(), - jsonCredentialCount - ); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount); }); test('can accept delete username & password credential via dialog', async function (assert) { @@ -218,16 +209,12 @@ module( assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - const jsonCredentialCount = - getJSONCredentialCount(); + const jsonCredentialCount = getJSONCredentialCount(); await visit(urls.jsonCredential); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); await click('.rose-dialog footer .rose-button-primary'); assert.strictEqual(currentURL(), urls.credentials); - assert.strictEqual( - getJSONCredentialCount(), - jsonCredentialCount - 1 - ); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount - 1); }); test('can cancel delete username & password credential via dialog', async function (assert) { @@ -266,16 +253,12 @@ module( assert.expect(2); const confirmService = this.owner.lookup('service:confirm'); confirmService.enabled = true; - const jsonCredentialCount = - getJSONCredentialCount(); + const jsonCredentialCount = getJSONCredentialCount(); await visit(urls.jsonCredential); await click('.rose-layout-page-actions .rose-dropdown-button-danger'); await click('.rose-dialog footer .rose-button-secondary'); assert.strictEqual(currentURL(), urls.jsonCredential); - assert.strictEqual( - getJSONCredentialCount(), - jsonCredentialCount - ); + assert.strictEqual(getJSONCredentialCount(), jsonCredentialCount); }); test('deleting a username & password credential which errors displays error message', async function (assert) { diff --git a/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js b/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js index 8b2db71f5..7853fbf62 100644 --- a/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js +++ b/ui/admin/tests/integration/components/form/field/json-secret-skeleton-test.js @@ -3,18 +3,21 @@ import { setupRenderingTest } from 'ember-qunit'; import { render } from '@ember/test-helpers'; import { hbs } from 'ember-cli-htmlbars'; -module('Integration | Component | form/field/json-secret/skeleton', function (hooks) { - setupRenderingTest(hooks); +module( + 'Integration | Component | form/field/json-secret/skeleton', + function (hooks) { + setupRenderingTest(hooks); - test('it renders', async function (assert) { - assert.expect(2); - await render(hbs` + test('it renders', async function (assert) { + assert.expect(2); + await render(hbs` Skeleton Message `); - assert.dom('.secret-editor-skeleton-wrapper').isVisible(); - assert.dom('.secret-editor-skeleton-message').hasText('Skeleton Message'); - }); -}); \ No newline at end of file + assert.dom('.secret-editor-skeleton-wrapper').isVisible(); + assert.dom('.secret-editor-skeleton-message').hasText('Skeleton Message'); + }); + } +); diff --git a/ui/admin/tests/integration/components/form/field/json-secret-test.js b/ui/admin/tests/integration/components/form/field/json-secret-test.js index d7f80cdb9..2b1112df0 100644 --- a/ui/admin/tests/integration/components/form/field/json-secret-test.js +++ b/ui/admin/tests/integration/components/form/field/json-secret-test.js @@ -26,7 +26,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').isVisible(); - assert.dom('.secret-editor-json .CodeMirror-line').hasText(this.model.json_object); + assert + .dom('.secret-editor-json .CodeMirror-line') + .hasText(this.model.json_object); }); test('it renders the disabled editor', async function (assert) { @@ -42,7 +44,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('This secret is saved but won\'t be displayed'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText("This secret is saved but won't be displayed"); }); test('it renders the actionable editor', async function (assert) { @@ -58,7 +62,9 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('Click to replace this secret'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText('Click to replace this secret'); }); test('it renders the active editor from the actionable view', async function (assert) { @@ -74,9 +80,11 @@ module('Integration | Component | form/field/json-secret', function (hooks) { assert.dom('.secret-editor').isVisible(); assert.dom('.secret-editor-json').doesNotExist(); - assert.dom('.secret-editor-skeleton-wrapper').hasText('Click to replace this secret'); + assert + .dom('.secret-editor-skeleton-wrapper') + .hasText('Click to replace this secret'); - await click('button') + await click('button'); assert.dom('.secret-editor-json').isVisible(); }); -}); \ No newline at end of file +}); diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js index 4a0e3ef78..a8a719f7e 100644 --- a/ui/admin/tests/unit/abilities/credential-test.js +++ b/ui/admin/tests/unit/abilities/credential-test.js @@ -4,63 +4,74 @@ import { setupTest } from 'ember-qunit'; module('Unit | Abilities | credential', function (hooks) { setupTest(hooks); - test('canRead', function (assert) { + let features; + + hooks.beforeEach(function () { + // Enable json-credentials feature by default + features = this.owner.lookup('service:features'); + features.enable('json-credentials'); + }); + + test('can read credentials, including JSON credentials, when authorized and json-credentials feature is enabled', function (assert) { assert.expect(3); const service = this.owner.lookup('service:can'); - const credential = { + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { authorized_actions: ['read'], type: 'json', - }; - assert.ok(service.can('read credential', credential)); + }); + assert.true(service.can('read credential', credential)); credential.type = 'username_password'; - assert.ok(service.can('read credential', credential)); + assert.true(service.can('read credential', credential)); credential.type = 'ssh_private_key'; - assert.ok(service.can('read credential', credential)); + assert.true(service.can('read credential', credential)); }); - test('cannot read without proper authorized actions', function (assert) { + test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is enabled', function (assert) { assert.expect(3); const service = this.owner.lookup('service:can'); - const credential = { + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { authorized_actions: [], type: 'json', - }; - assert.notOk(service.can('read credential', credential)); + }); + assert.false(service.can('read credential', credential)); credential.type = 'username_password'; - assert.notOk(service.can('read credential', credential)); + assert.false(service.can('read credential', credential)); credential.type = 'ssh_private_key'; - assert.notOk(service.can('read credential', credential)); + assert.false(service.can('read credential', credential)); }); - test('cannot read without proper authorized actions and json-credentials disabled', function (assert) { + test('cannot read credentials, including JSON credentials, when unauthorized and json-credentials feature is disabled', function (assert) { assert.expect(3); const service = this.owner.lookup('service:can'); const featuresService = this.owner.lookup('service:features'); featuresService.disable('json-credentials'); - const credential = { + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { authorized_actions: [], type: 'json', - }; - assert.notOk(service.can('read credential', credential)); + }); + assert.false(service.can('read credential', credential)); credential.type = 'username_password'; - assert.notOk(service.can('read credential', credential)); + assert.false(service.can('read credential', credential)); credential.type = 'ssh_private_key'; - assert.notOk(service.can('read credential', credential)); + assert.false(service.can('read credential', credential)); }); - test('cannot read when json-credentials is disabled', function (assert) { + test('can read credentials, excepting JSON credentials, when authorized and json-credentials feature is disabled', function (assert) { assert.expect(3); + features.disable('json-credentials'); const service = this.owner.lookup('service:can'); - const featuresService = this.owner.lookup('service:features'); - featuresService.disable('json-credentials'); - const credential = { + const store = this.owner.lookup('service:store'); + const credential = store.createRecord('credential', { authorized_actions: ['read'], type: 'json', - }; - assert.notOk(service.can('read credential', credential)); + }); + assert.false(service.can('read credential', credential)); credential.type = 'username_password'; - assert.ok(service.can('read credential', credential)); + assert.true(service.can('read credential', credential)); credential.type = 'ssh_private_key'; - assert.ok(service.can('read credential', credential)); + assert.true(service.can('read credential', credential)); }); }); From 54c5c7fdc5f6f8ce013295fd8d1381c8af464f84 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Mon, 19 Dec 2022 16:59:51 -0500 Subject: [PATCH 18/25] Chore tweak target type content (#1529) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 🐛 refactor info-field component Overall context of how the `` was changed to the correct order. Corrected css grid to have the order of `label,input-field,helper-text`. Also refactored all component instances affected by the change. * refactor: 💡 refactor styling for icon to be center refactor will get the icon as close to center as possible. * chore: 🤖 ssh type first; update names and descriptions * chore: 🤖 tweak info field styles * chore: 🤖 fix formatting/linting issue * refactor: 💡 credential stylistic changes per PR feedback * chore: 🤖 reuse configPath * fix: 🐛 fix lint error * chore: 🤖 add target type field helper text Co-authored-by: dvonanderson --- addons/core/translations/resources/en-us.yaml | 10 +++++--- addons/rose/addon/modifiers/code-mirror.js | 4 +-- .../components/form/target/details/index.hbs | 25 ++++++++++--------- .../components/form/target/details/index.js | 19 ++++++++------ .../app/routes/scopes/scope/targets/new.js | 9 +++++-- .../tests/acceptance/targets/create-test.js | 19 +++++++------- 6 files changed, 50 insertions(+), 36 deletions(-) diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 46f93a131..035a8e4ae 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -304,11 +304,15 @@ target: add-brokered-credential-sources: Add Brokered Credentials add-injected-application-credential-sources: Add Injected Application Credentials types: - tcp: TCP + tcp: Generic TCP ssh: SSH help: - tcp: Connections are made over TCP to support a broad range of protocols, including SSH, RDP, databases, and more. This is the most general-purpose type. - ssh: Protocol-aware connections are made over SSH. Use when requiring SSH connections to remote hosts. + tcp: Generic TCP supports a broad range of connection types. + ssh: Protocol-aware SSH with support for credential injection. + form: + type: + label: Type + help: Target type is the protocol with which end users should connect to this target. Choose Generic TCP for broad support of common protocols including RDP, K8s, many databases, and more. host-source: title: Host Source title_plural: Host Sources diff --git a/addons/rose/addon/modifiers/code-mirror.js b/addons/rose/addon/modifiers/code-mirror.js index 077d98d25..3a0e66aab 100644 --- a/addons/rose/addon/modifiers/code-mirror.js +++ b/addons/rose/addon/modifiers/code-mirror.js @@ -9,8 +9,8 @@ import 'codemirror/addon/edit/matchbrackets'; import 'codemirror/addon/edit/closebrackets'; import 'codemirror/addon/selection/active-line'; import 'codemirror/addon/display/autorefresh'; -import 'codemirror/addon/lint/lint' -import 'codemirror/addon/lint/json-lint' +import 'codemirror/addon/lint/lint'; +import 'codemirror/addon/lint/json-lint'; // Here we define default options for the editor. // These should follow the codemirror configuration types diff --git a/ui/admin/app/components/form/target/details/index.hbs b/ui/admin/app/components/form/target/details/index.hbs index 9bdb90a2b..645ba7584 100644 --- a/ui/admin/app/components/form/target/details/index.hbs +++ b/ui/admin/app/components/form/target/details/index.hbs @@ -43,25 +43,25 @@ {{#if (and @model.isNew (feature-flag 'ssh-target'))}} - {{t 'form.type.label'}} - {{#each-in this.mapResourceTypeWithIcon as |targetType icon|}} + {{t 'resources.target.form.type.label'}} + {{t 'resources.target.form.type.help'}} + {{#each this.typeMetas as |type|}} - {{t (concat 'resources.target.types.' targetType)}} + {{t (concat 'resources.target.types.' type.type)}} {{t - (concat 'resources.target.help.' targetType) + (concat 'resources.target.help.' type.type) }} - + - {{/each-in}} + {{/each}} - +
{{else}} {{t 'form.type.label'}} - {{t (concat 'resources.target.help.' @model.type)}} + + {{t (concat 'resources.target.help.' @model.type)}} {{/if}} diff --git a/ui/admin/app/components/form/target/details/index.js b/ui/admin/app/components/form/target/details/index.js index d37de8a08..4085ab018 100644 --- a/ui/admin/app/components/form/target/details/index.js +++ b/ui/admin/app/components/form/target/details/index.js @@ -2,7 +2,10 @@ import Component from '@glimmer/component'; import { types } from 'api/models/target'; //Note: this is a temporary solution till we have resource type helper in place -const icons = ['network', 'terminal']; +const icons = { + ssh: 'terminal-screen', + tcp: 'network', +}; export default class FormTargetComponent extends Component { // =properties @@ -10,8 +13,12 @@ export default class FormTargetComponent extends Component { * maps resource type with icon * @type {object} */ - get mapResourceTypeWithIcon() { - return types.reduce((obj, type, i) => ({ ...obj, [type]: icons[i] }), {}); + get typeMetas() { + const reversedTypes = [...types].reverse(); + return reversedTypes.map((type) => ({ + type, + icon: icons[type], + })); } /** @@ -20,10 +27,6 @@ export default class FormTargetComponent extends Component { * @type {string} */ get icon() { - if (this.args.model.type === 'tcp') { - return icons[0]; - } else { - return icons[1]; - } + return icons[this.args.model.type]; } } diff --git a/ui/admin/app/routes/scopes/scope/targets/new.js b/ui/admin/app/routes/scopes/scope/targets/new.js index 7aae3db3d..7a3f412ef 100644 --- a/ui/admin/app/routes/scopes/scope/targets/new.js +++ b/ui/admin/app/routes/scopes/scope/targets/new.js @@ -7,6 +7,7 @@ export default class ScopesScopeTargetsNewRoute extends Route { @service store; @service router; + @service features; // =methods @@ -23,9 +24,13 @@ export default class ScopesScopeTargetsNewRoute extends Route { * @return {TargetModel} */ - model({ type = 'tcp' }) { - const scopeModel = this.modelFor('scopes.scope'); + model({ type }) { let name, description; + const scopeModel = this.modelFor('scopes.scope'); + + // default type is SSH if the feature is enabled, otherwise TCP + if (!type) type = this.features.isEnabled('ssh-target') ? 'ssh' : 'tcp'; + if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); diff --git a/ui/admin/tests/acceptance/targets/create-test.js b/ui/admin/tests/acceptance/targets/create-test.js index 856d33fd2..cfcfb8317 100644 --- a/ui/admin/tests/acceptance/targets/create-test.js +++ b/ui/admin/tests/acceptance/targets/create-test.js @@ -77,25 +77,26 @@ module('Acceptance | targets | create', function (hooks) { authenticateSession({}); }); - test('defaults to a new TCP target when no query param provided', async function (assert) { + test('defaults to type `ssh` when no query param provided', async function (assert) { assert.expect(1); await visit(urls.newTarget); - assert.strictEqual(find('input[name=tcp]:checked').value, 'tcp'); + assert.strictEqual(find('[name="type"]:checked').value, 'ssh'); }); - test('can create new targets of type TCP', async function (assert) { + test('can create type `ssh`', async function (assert) { assert.expect(4); const targetCount = getTargetCount(); - const tcpTargetCount = getTCPTargetCount(); + const sshTargetCount = getSSHTargetCount(); await visit(urls.targets); await click(`[href="${urls.newTarget}"]`); + await click('[value="ssh"]'); await fillIn('[name="name"]', 'random string'); await fillIn('[name="worker_filter"]', 'random filter'); await click('[type="submit"]'); + assert.strictEqual(getSSHTargetCount(), sshTargetCount + 1); assert.strictEqual(getTargetCount(), targetCount + 1); - assert.strictEqual(getTCPTargetCount(), tcpTargetCount + 1); assert.strictEqual( this.server.schema.targets.all().models[getTargetCount() - 1].name, 'random string' @@ -107,20 +108,20 @@ module('Acceptance | targets | create', function (hooks) { ); }); - test('can create new targets of type SSH', async function (assert) { + test('can create type `tcp`', async function (assert) { assert.expect(4); const targetCount = getTargetCount(); - const sshTargetCount = getSSHTargetCount(); + const tcpTargetCount = getTCPTargetCount(); await visit(urls.targets); await click(`[href="${urls.newTarget}"]`); + await click('[value="tcp"]'); await fillIn('[name="name"]', 'random string'); - await click('[value="ssh"]'); await fillIn('[name="worker_filter"]', 'random filter'); await click('[type="submit"]'); - assert.strictEqual(getSSHTargetCount(), sshTargetCount + 1); assert.strictEqual(getTargetCount(), targetCount + 1); + assert.strictEqual(getTCPTargetCount(), tcpTargetCount + 1); assert.strictEqual( this.server.schema.targets.all().models[getTargetCount() - 1].name, 'random string' From 473ebcc3971430b70bd4beb7af2fd9908f1d9b3d Mon Sep 17 00:00:00 2001 From: Zhihe Li Date: Tue, 20 Dec 2022 11:29:13 -0500 Subject: [PATCH 19/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Bump=20loader-uti?= =?UTF-8?q?ls=20to=202.0.4=20(#1537)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 ++- yarn.lock | 24 ++++-------------------- 2 files changed, 6 insertions(+), 21 deletions(-) diff --git a/package.json b/package.json index 67cce2a4a..feb715b38 100755 --- a/package.json +++ b/package.json @@ -105,7 +105,8 @@ "async": "^2.6.4", "terser": "^5.14.2", "jpeg-js": "^0.4.4", - "got": "^11.8.5" + "got": "^11.8.5", + "loader-utils": "^2.0.4" }, "config": { "commitizen": { diff --git a/yarn.lock b/yarn.lock index 573d3c886..4ffd0df32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17850,13 +17850,6 @@ json5@^0.5.1: resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= -json5@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" - integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== - dependencies: - minimist "^1.2.0" - json5@^2.1.2, json5@^2.1.3, json5@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" @@ -18317,24 +18310,15 @@ loader-runner@^4.2.0: resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw== -loader-utils@2.0.0, loader-utils@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0" - integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ== +loader-utils@2.0.0, loader-utils@^1.2.3, loader-utils@^1.4.0, loader-utils@^2.0.0, loader-utils@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== dependencies: big.js "^5.2.2" emojis-list "^3.0.0" json5 "^2.1.2" -loader-utils@^1.2.3, loader-utils@^1.4.0: - version "1.4.2" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.4.2.tgz#29a957f3a63973883eb684f10ffd3d151fec01a3" - integrity sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg== - dependencies: - big.js "^5.2.2" - emojis-list "^3.0.0" - json5 "^1.0.1" - loader.js@^4.7.0: version "4.7.0" resolved "https://registry.yarnpkg.com/loader.js/-/loader.js-4.7.0.tgz#a1a52902001c83631efde9688b8ab3799325ef1f" From 69dfc7c784b42f1bf801c30fcaab4dd1e1f61b63 Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 20 Dec 2022 12:07:52 -0500 Subject: [PATCH 20/25] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20use=20constant?= =?UTF-8?q?s=20for=20target=20types=20(#1538)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 use constants for target types * chore: 🤖 import types rather than * * chore: 🤖 use capitalized TYPES_TARGET constant name --- addons/api/addon/models/target.js | 8 +++++--- addons/api/mirage/factories/target.js | 15 +++++++-------- addons/api/mirage/generated/factories/target.js | 3 ++- addons/api/tests/unit/models/target-test.js | 5 +++-- .../tests/unit/serializers/auth-method-test.js | 1 - addons/api/tests/unit/serializers/target-test.js | 9 +++++---- .../app/components/form/target/details/index.js | 8 ++++---- .../onboarding/quick-setup/create-resources.js | 3 ++- ui/admin/app/routes/scopes/scope/targets/new.js | 6 +++++- ui/admin/tests/acceptance/targets/create-test.js | 7 ++++--- ...njected-application-credential-sources-test.js | 3 ++- ui/admin/tests/acceptance/targets/read-test.js | 5 +++-- .../tests/acceptance/projects/targets-test.js | 3 ++- 13 files changed, 44 insertions(+), 32 deletions(-) diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 08db9715c..5a77ce566 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -2,7 +2,9 @@ import GeneratedTargetModel from '../generated/models/target'; import { attr } from '@ember-data/model'; import { inject as service } from '@ember/service'; -export const types = ['tcp', 'ssh']; +export const TYPE_TARGET_TCP = 'tcp'; +export const TYPE_TARGET_SSH = 'ssh'; +export const TYPES_TARGET = Object.freeze([TYPE_TARGET_TCP, TYPE_TARGET_SSH]); export default class TargetModel extends GeneratedTargetModel { // =services @@ -321,7 +323,7 @@ export default class TargetModel extends GeneratedTargetModel { * @type {boolean} */ get isTCP() { - return this.type === 'tcp'; + return this.type === TYPE_TARGET_TCP; } /** @@ -329,6 +331,6 @@ export default class TargetModel extends GeneratedTargetModel { * @type {boolean} */ get isSSH() { - return this.type === 'ssh'; + return this.type === TYPE_TARGET_SSH; } } diff --git a/addons/api/mirage/factories/target.js b/addons/api/mirage/factories/target.js index 9ea10477e..8afdb93d3 100644 --- a/addons/api/mirage/factories/target.js +++ b/addons/api/mirage/factories/target.js @@ -3,10 +3,11 @@ import { trait } from 'ember-cli-mirage'; import { faker } from '@faker-js/faker'; import permissions from '../helpers/permissions'; import generateId from '../helpers/id'; +import { TYPES_TARGET } from 'api/models/target'; const randomBoolean = (chance = 0.5) => Math.random() < chance; const hostSetChance = 0.3; -const types = ['tcp', 'ssh']; +const types = [...TYPES_TARGET]; export default factory.extend({ authorized_actions: () => @@ -35,13 +36,11 @@ export default factory.extend({ * Generates attributes fields by type. */ afterCreate(target) { - if (target.type === 'tcp' || target.type === 'ssh') { - target.update({ - attributes: { - default_port: faker.datatype.number(), - }, - }); - } + target.update({ + attributes: { + default_port: faker.datatype.number(), + }, + }); }, /** diff --git a/addons/api/mirage/generated/factories/target.js b/addons/api/mirage/generated/factories/target.js index 35e072290..d714b6459 100644 --- a/addons/api/mirage/generated/factories/target.js +++ b/addons/api/mirage/generated/factories/target.js @@ -1,11 +1,12 @@ import { Factory } from 'ember-cli-mirage'; import { faker } from '@faker-js/faker'; +import { TYPE_TARGET_TCP } from 'api/models/target'; /** * GeneratedTargetModelFactory */ export default Factory.extend({ - type: 'tcp', + type: TYPE_TARGET_TCP, name: () => faker.random.words(), session_max_seconds: () => faker.datatype.number(), session_connection_limit: () => faker.datatype.number(), diff --git a/addons/api/tests/unit/models/target-test.js b/addons/api/tests/unit/models/target-test.js index eca4d54a5..86dd93c0f 100644 --- a/addons/api/tests/unit/models/target-test.js +++ b/addons/api/tests/unit/models/target-test.js @@ -1,6 +1,7 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Unit | Model | target', function (hooks) { setupTest(hooks); @@ -383,7 +384,7 @@ module('Unit | Model | target', function (hooks) { assert.expect(2); const store = this.owner.lookup('service:store'); const modelSSH = store.createRecord('target', { - type: 'ssh', + type: TYPE_TARGET_SSH, }); assert.true(modelSSH.isSSH); assert.false(modelSSH.isTCP); @@ -393,7 +394,7 @@ module('Unit | Model | target', function (hooks) { assert.expect(2); const store = this.owner.lookup('service:store'); const modelTCP = store.createRecord('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, }); assert.true(modelTCP.isTCP); assert.false(modelTCP.isSSH); diff --git a/addons/api/tests/unit/serializers/auth-method-test.js b/addons/api/tests/unit/serializers/auth-method-test.js index 17043ffba..23c17fb7e 100644 --- a/addons/api/tests/unit/serializers/auth-method-test.js +++ b/addons/api/tests/unit/serializers/auth-method-test.js @@ -182,7 +182,6 @@ module('Unit | Serializer | auth method', function (hooks) { signing_algorithms, idp_ca_certs, } = record; - console.log(record); assert.deepEqual(account_claim_maps, [ { from: 'from', to: 'to' }, { from: 'foo', to: 'bar' }, diff --git a/addons/api/tests/unit/serializers/target-test.js b/addons/api/tests/unit/serializers/target-test.js index fde87e20d..29864c838 100644 --- a/addons/api/tests/unit/serializers/target-test.js +++ b/addons/api/tests/unit/serializers/target-test.js @@ -1,5 +1,6 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Unit | Serializer | target', function (hooks) { setupTest(hooks); @@ -26,7 +27,7 @@ module('Unit | Serializer | target', function (hooks) { }, default_port: 1234, version: 1, - type: 'tcp', + type: TYPE_TARGET_TCP, }); const snapshot = record._createSnapshot(); snapshot.adapterOptions = {}; @@ -35,7 +36,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'tcp', + type: TYPE_TARGET_TCP, scope_id: 'org_1', session_max_seconds: 28800, session_connection_limit: null, @@ -124,7 +125,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'ssh', + type: TYPE_TARGET_SSH, worker_filter: 'worker', default_port: 1234, scope: { @@ -137,7 +138,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'User', description: 'Description', version: 1, - type: 'ssh', + type: TYPE_TARGET_SSH, scope_id: 'org_1', session_max_seconds: 28800, session_connection_limit: null, diff --git a/ui/admin/app/components/form/target/details/index.js b/ui/admin/app/components/form/target/details/index.js index 4085ab018..0430fcd80 100644 --- a/ui/admin/app/components/form/target/details/index.js +++ b/ui/admin/app/components/form/target/details/index.js @@ -1,7 +1,8 @@ import Component from '@glimmer/component'; -import { types } from 'api/models/target'; +import { TYPES_TARGET } from 'api/models/target'; -//Note: this is a temporary solution till we have resource type helper in place +// NOTE: this is all a temporary solution till we have a resource type helper. +const types = [...TYPES_TARGET].reverse(); const icons = { ssh: 'terminal-screen', tcp: 'network', @@ -14,8 +15,7 @@ export default class FormTargetComponent extends Component { * @type {object} */ get typeMetas() { - const reversedTypes = [...types].reverse(); - return reversedTypes.map((type) => ({ + return types.map((type) => ({ type, icon: icons[type], })); diff --git a/ui/admin/app/routes/onboarding/quick-setup/create-resources.js b/ui/admin/app/routes/onboarding/quick-setup/create-resources.js index d55239c7d..cc9f540ea 100644 --- a/ui/admin/app/routes/onboarding/quick-setup/create-resources.js +++ b/ui/admin/app/routes/onboarding/quick-setup/create-resources.js @@ -3,6 +3,7 @@ import { inject as service } from '@ember/service'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { notifyError } from 'core/decorators/notify'; +import { TYPE_TARGET_TCP } from 'api/models/target'; export default class OnboardingQuickSetupCreateResourcesRoute extends Route { // =services @@ -40,7 +41,7 @@ export default class OnboardingQuickSetupCreateResourcesRoute extends Route { description: 'Sample host created by quick setup', }), target: this.store.createRecord('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, name: 'EC2 Instances', description: 'Sample target created by quick setup', }), diff --git a/ui/admin/app/routes/scopes/scope/targets/new.js b/ui/admin/app/routes/scopes/scope/targets/new.js index 7a3f412ef..d43e0cc4e 100644 --- a/ui/admin/app/routes/scopes/scope/targets/new.js +++ b/ui/admin/app/routes/scopes/scope/targets/new.js @@ -1,6 +1,7 @@ import Route from '@ember/routing/route'; import { action } from '@ember/object'; import { inject as service } from '@ember/service'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; export default class ScopesScopeTargetsNewRoute extends Route { // =services @@ -29,7 +30,10 @@ export default class ScopesScopeTargetsNewRoute extends Route { const scopeModel = this.modelFor('scopes.scope'); // default type is SSH if the feature is enabled, otherwise TCP - if (!type) type = this.features.isEnabled('ssh-target') ? 'ssh' : 'tcp'; + if (!type) + type = this.features.isEnabled('ssh-target') + ? TYPE_TARGET_SSH + : TYPE_TARGET_TCP; if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); diff --git a/ui/admin/tests/acceptance/targets/create-test.js b/ui/admin/tests/acceptance/targets/create-test.js index cfcfb8317..cdf741c34 100644 --- a/ui/admin/tests/acceptance/targets/create-test.js +++ b/ui/admin/tests/acceptance/targets/create-test.js @@ -16,6 +16,7 @@ import { //currentSession, //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | targets | create', function (hooks) { setupApplicationTest(hooks); @@ -71,16 +72,16 @@ module('Acceptance | targets | create', function (hooks) { // Generate resource counter getTargetCount = () => this.server.schema.targets.all().models.length; getSSHTargetCount = () => - this.server.schema.targets.where({ type: 'ssh' }).models.length; + this.server.schema.targets.where({ type: TYPE_TARGET_SSH }).models.length; getTCPTargetCount = () => - this.server.schema.targets.where({ type: 'tcp' }).models.length; + this.server.schema.targets.where({ type: TYPE_TARGET_TCP }).models.length; authenticateSession({}); }); test('defaults to type `ssh` when no query param provided', async function (assert) { assert.expect(1); await visit(urls.newTarget); - assert.strictEqual(find('[name="type"]:checked').value, 'ssh'); + assert.strictEqual(find('[name="type"]:checked').value, TYPE_TARGET_SSH); }); test('can create type `ssh`', async function (assert) { diff --git a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js index fed2a6956..feccc7ee5 100644 --- a/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/injected-application-credential-sources-test.js @@ -5,6 +5,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { Response } from 'miragejs'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_SSH } from 'api/models/target'; module( 'Acceptance | targets | injected application credential sources', @@ -78,7 +79,7 @@ module( instances.credential = instances.credentials[0]; instances.target = this.server.create('target', { scope: instances.scopes.project, - type: 'ssh', + type: TYPE_TARGET_SSH, }); randomlySelectedCredentials = this.server.schema.credentials .all() diff --git a/ui/admin/tests/acceptance/targets/read-test.js b/ui/admin/tests/acceptance/targets/read-test.js index 5eaaba87a..e49df488f 100644 --- a/ui/admin/tests/acceptance/targets/read-test.js +++ b/ui/admin/tests/acceptance/targets/read-test.js @@ -9,6 +9,7 @@ import { //currentSession, //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_TCP, TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | targets | read', function (hooks) { setupApplicationTest(hooks); @@ -44,11 +45,11 @@ module('Acceptance | targets | read', function (hooks) { scope: { id: instances.scopes.org.id, type: 'org' }, }); instances.sshTarget = this.server.create('target', { - type: 'ssh', + type: TYPE_TARGET_SSH, scope: instances.scopes.project, }); instances.tcpTarget = this.server.create('target', { - type: 'tcp', + type: TYPE_TARGET_TCP, scope: instances.scopes.project, }); // Generate route URLs for resources diff --git a/ui/desktop/tests/acceptance/projects/targets-test.js b/ui/desktop/tests/acceptance/projects/targets-test.js index cb4d2c7ba..d08ff81bf 100644 --- a/ui/desktop/tests/acceptance/projects/targets-test.js +++ b/ui/desktop/tests/acceptance/projects/targets-test.js @@ -10,6 +10,7 @@ import { authenticateSession, invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_SSH } from 'api/models/target'; module('Acceptance | projects | targets', function (hooks) { setupApplicationTest(hooks); @@ -247,7 +248,7 @@ module('Acceptance | projects | targets', function (hooks) { test('displays the correct target type (SSH) in the success dialog body', async function (assert) { assert.expect(1); instances.target.update({ - type: 'ssh', + type: TYPE_TARGET_SSH, }); stubs.ipcService.withArgs('cliExists').returns(true); stubs.ipcService.withArgs('connect').returns({ From 7dd9fb80d5f1c37e83f83f1e6e30f34a480e6b3d Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Tue, 20 Dec 2022 16:31:39 -0500 Subject: [PATCH 21/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20add=20role=20read?= =?UTF-8?q?Principal=20ability=20(#1540)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/addon/abilities/managed-group.js | 8 +++ addons/api/addon/abilities/role.js | 27 +++++++++++ addons/api/app/abilities/managed-group.js | 1 + addons/api/tests/unit/abilities/role-test.js | 51 +++++++++++++++++--- 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 addons/api/addon/abilities/managed-group.js create mode 100644 addons/api/app/abilities/managed-group.js diff --git a/addons/api/addon/abilities/managed-group.js b/addons/api/addon/abilities/managed-group.js new file mode 100644 index 000000000..dd02a34e9 --- /dev/null +++ b/addons/api/addon/abilities/managed-group.js @@ -0,0 +1,8 @@ +import ModelAbility from './model'; + +/** + * Provides abilities for managed groups. + */ +export default class ManagedGroupAbility extends ModelAbility { + // no op +} diff --git a/addons/api/addon/abilities/role.js b/addons/api/addon/abilities/role.js index f0c6d62ec..b944fa548 100644 --- a/addons/api/addon/abilities/role.js +++ b/addons/api/addon/abilities/role.js @@ -1,9 +1,18 @@ import ModelAbility from './model'; +import { inject as service } from '@ember/service'; + +class InvalidRolePrincipalTypeError extends Error { + name = 'InvalidRolePrincipalTypeError'; +} /** * Provides abilities for roles. */ export default class RoleAbility extends ModelAbility { + // =services + + @service can; + // =permissions /** @@ -26,4 +35,22 @@ export default class RoleAbility extends ModelAbility { get canRemovePrincipals() { return this.hasAuthorizedAction('remove-principals'); } + + /** + * Returns the ability check for a principal model instance. + * Valid role principal models are: User, Group, and ManagedGroup. + * Throws an error if the principal type is invalid. + * @type {boolean} + * @throws {InvalidRolePrincipalTypeError} + */ + get canReadPrincipal() { + const type = this.model.constructor.modelName; + const typeIsValid = + type === 'user' || type === 'group' || type === 'managed-group'; + if (!typeIsValid) + throw new InvalidRolePrincipalTypeError( + `Expected a role principal of type 'user', 'group', or 'managed-group'. Got '${type}'.` + ); + return this.can.can(`read ${type}`, this.model); + } } diff --git a/addons/api/app/abilities/managed-group.js b/addons/api/app/abilities/managed-group.js new file mode 100644 index 000000000..bf7e304a5 --- /dev/null +++ b/addons/api/app/abilities/managed-group.js @@ -0,0 +1 @@ +export { default } from 'api/abilities/managed-group'; diff --git a/addons/api/tests/unit/abilities/role-test.js b/addons/api/tests/unit/abilities/role-test.js index 462743446..b2e8675e5 100644 --- a/addons/api/tests/unit/abilities/role-test.js +++ b/addons/api/tests/unit/abilities/role-test.js @@ -10,9 +10,9 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['set-grants'], }; - assert.ok(service.can('setGrants role', model)); + assert.true(service.can('setGrants role', model)); model.authorized_actions = []; - assert.notOk(service.can('setGrants role', model)); + assert.false(service.can('setGrants role', model)); }); test('it reflects when a given role may add principals based on authorized_actions', function (assert) { @@ -21,9 +21,9 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['add-principals'], }; - assert.ok(service.can('addPrincipals role', model)); + assert.true(service.can('addPrincipals role', model)); model.authorized_actions = []; - assert.notOk(service.can('addPrincipals role', model)); + assert.false(service.can('addPrincipals role', model)); }); test('it reflects when a given role may remove principals based on authorized_actions', function (assert) { @@ -32,8 +32,47 @@ module('Unit | Abilities | Role', function (hooks) { const model = { authorized_actions: ['remove-principals'], }; - assert.ok(service.can('removePrincipals role', model)); + assert.true(service.can('removePrincipals role', model)); model.authorized_actions = []; - assert.notOk(service.can('removePrincipals role', model)); + assert.false(service.can('removePrincipals role', model)); + }); + + test('it proxies role principal read ability to the appropriate ability type', function (assert) { + assert.expect(6); + const service = this.owner.lookup('service:can'); + const user = { + constructor: { modelName: 'user' }, + authorized_actions: [], + }; + const group = { + constructor: { modelName: 'group' }, + authorized_actions: [], + }; + const managedGroup = { + constructor: { modelName: 'managed-group' }, + authorized_actions: [], + }; + assert.false(service.can('readPrincipal role', user)); + assert.false(service.can('readPrincipal role', group)); + assert.false(service.can('readPrincipal role', managedGroup)); + user.authorized_actions.push('read'); + group.authorized_actions.push('read'); + managedGroup.authorized_actions.push('read'); + assert.true(service.can('readPrincipal role', user)); + assert.true(service.can('readPrincipal role', group)); + assert.true(service.can('readPrincipal role', managedGroup)); + }); + + test('it throws an error for unknown principal types on principal read check', function (assert) { + assert.expect(2); + const service = this.owner.lookup('service:can'); + const account = { + constructor: { modelName: 'account' }, + authorized_actions: ['read'], + }; + assert.throws(() => { + service.can('readPrincipal role', account); + }); + assert.true(service.can('read account', account)); }); }); From e9f1de91a1f047dd1a5c236b01684d274333853d Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Wed, 21 Dec 2022 09:42:13 -0800 Subject: [PATCH 22/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20update=20role=20f?= =?UTF-8?q?orm=20fields=20to=20hds=20(#1539)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/admin/app/components/form/role/index.hbs | 53 ++++++++++--------- .../tests/acceptance/roles/create-test.js | 2 +- .../tests/acceptance/roles/update-test.js | 2 +- 3 files changed, 30 insertions(+), 27 deletions(-) diff --git a/ui/admin/app/components/form/role/index.hbs b/ui/admin/app/components/form/role/index.hbs index 1a6a9a94e..a20c290f4 100644 --- a/ui/admin/app/components/form/role/index.hbs +++ b/ui/admin/app/components/form/role/index.hbs @@ -6,44 +6,47 @@ as |form| > - + {{t 'form.name.label'}} + {{t 'form.name.help'}} {{#if @model.errors.name}} - + {{#each @model.errors.name as |error|}} - {{error.message}} + {{error.message}} {{/each}} - + {{/if}} - + - + {{t 'form.description.label'}} + {{t 'form.description.help'}} {{#if @model.errors.description}} - + {{#each @model.errors.description as |error|}} - {{error.message}} + {{error.message}} {{/each}} - + {{/if}} - + {{#unless @model.isNew}}
Date: Thu, 22 Dec 2022 11:05:31 -0500 Subject: [PATCH 23/25] =?UTF-8?q?chore:=20=F0=9F=A4=96=20Update=20xmldom?= =?UTF-8?q?=20to=20patch=20security=20vulnerability=20(#1541)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 4ffd0df32..31dced969 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7391,9 +7391,9 @@ "@xtuc/long" "4.2.2" "@xmldom/xmldom@^0.8.0": - version "0.8.2" - resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.2.tgz#b695ff674e8216efa632a3d36ad51ae9843380c0" - integrity sha512-+R0juSseERyoPvnBQ/cZih6bpF7IpCXlWbHRoCRzYzqpz6gWHOgf8o4MOEf6KBVuOyqU+gCNLkCWVIJAro8XyQ== + version "0.8.6" + resolved "https://registry.yarnpkg.com/@xmldom/xmldom/-/xmldom-0.8.6.tgz#8a1524eb5bd5e965c1e3735476f0262469f71440" + integrity sha512-uRjjusqpoqfmRkTaNuLJ2VohVr67Q5YwDATW3VU7PfzTj6IRaihGrYI7zckGZjxQPBIp63nfvJbM+Yu5ICh0Bg== "@xtuc/ieee754@^1.2.0": version "1.2.0" From 392da009d0fe76b82f24a2ec50f8909d24da0d63 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Thu, 22 Dec 2022 10:23:49 -0800 Subject: [PATCH 24/25] Add mirage support principal managed groups (#1542) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 mocks for principals/managed-groups & roles --- addons/api/mirage/config.js | 33 ++++++++++++--- addons/api/mirage/factories/role.js | 6 ++- addons/api/mirage/models/role.js | 1 + addons/api/mirage/scenarios/default.js | 41 +++++++++++++++++-- addons/api/mirage/serializers/role.js | 14 ++++++- addons/core/translations/resources/en-us.yaml | 1 + .../tests/acceptance/roles/principals-test.js | 7 ++-- 7 files changed, 90 insertions(+), 13 deletions(-) diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 95bbe9ac7..d18bc0676 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -267,14 +267,17 @@ export default function () { this.del('/roles/:id'); this.post( '/roles/:idMethod', - function ({ roles, users, groups }, { params: { idMethod } }) { + function ( + { roles, users, groups, managedGroups }, + { params: { idMethod } } + ) { const attrs = this.normalizedRequestAttrs(); const id = idMethod.split(':')[0]; const method = idMethod.split(':')[1]; const role = roles.find(id); let updatedAttrs = {}; - // Principals is a combined list of users and groups, but in Mirage we've + // Principals is a combined list of users, groups and managed groups, but in Mirage we've // internally modelled them as separate lists. Therefore we must check // the type by looking up the user or group first, then determine which // list to add the principal to. @@ -283,25 +286,31 @@ export default function () { version: attrs.version, userIds: role.userIds, groupIds: role.groupIds, + managedGroupIds: role.managedGroupIds, }; attrs.principalIds.forEach((id) => { const isUser = users.find(id); const isGroup = groups.find(id); + const isManagedGroup = managedGroups.find(id); if (isUser && !updatedAttrs.userIds.includes(id)) { updatedAttrs.userIds.push(id); } if (isGroup && !updatedAttrs.groupIds.includes(id)) { updatedAttrs.groupIds.push(id); } + if (isManagedGroup && !updatedAttrs.managedGroupIds.includes(id)) { + updatedAttrs.managedGroupIds.push(id); + } }); } - // If deleting principals, filter them out of both users and groups, + // If deleting principals, filter the ids out of users, groups and managed group lists, // and in this case don't care about the type if (method === 'remove-principals') { updatedAttrs = { version: attrs.version, userIds: role.userIds, groupIds: role.groupIds, + managedGroupIds: role.managedGroupIds, }; updatedAttrs.userIds = updatedAttrs.userIds.filter((id) => { return !attrs.principalIds.includes(id); @@ -309,6 +318,11 @@ export default function () { updatedAttrs.groupIds = updatedAttrs.groupIds.filter((id) => { return !attrs.principalIds.includes(id); }); + updatedAttrs.managedGroupIds = updatedAttrs.managedGroupIds.filter( + (id) => { + return !attrs.principalIds.includes(id); + } + ); } if (method === 'set-grants') { @@ -580,8 +594,17 @@ export default function () { // managed-groups this.get( '/managed-groups', - ({ managedGroups }, { queryParams: { auth_method_id: authMethodId } }) => { - return managedGroups.where({ authMethodId }); + ( + { managedGroups }, + { queryParams: { auth_method_id: authMethodId, filter } } + ) => { + let resultSet; + if (authMethodId) { + resultSet = managedGroups.where({ authMethodId }); + } else { + resultSet = managedGroups.all(); + } + return resultSet.filter(makeBooleanFilter(filter)); } ); this.post('/managed-groups'); diff --git a/addons/api/mirage/factories/role.js b/addons/api/mirage/factories/role.js index 841558fde..72138f61c 100644 --- a/addons/api/mirage/factories/role.js +++ b/addons/api/mirage/factories/role.js @@ -28,9 +28,13 @@ export default factory.extend({ withPrincipals: trait({ afterCreate(role, server) { const { scope } = role; + const { id: scopeId } = scope; const users = server.createList('user', 2, { scope }); const groups = server.createList('group', 2, { scope }); - role.update({ users, groups }); + const managedGroups = server.schema.managedGroups.where({ + scopeId, + }).models; + role.update({ users, groups, managedGroups }); }, }), diff --git a/addons/api/mirage/models/role.js b/addons/api/mirage/models/role.js index 97be034f9..54acf6d50 100644 --- a/addons/api/mirage/models/role.js +++ b/addons/api/mirage/models/role.js @@ -5,4 +5,5 @@ export default Model.extend({ scope: belongsTo(), users: hasMany(), groups: hasMany(), + managedGroups: hasMany(), }); diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js index 113dd3194..7ce918221 100644 --- a/addons/api/mirage/scenarios/default.js +++ b/addons/api/mirage/scenarios/default.js @@ -20,7 +20,7 @@ export default function (server) { // Auth const globalAuthMethods = server.createList( 'auth-method', - 1, + 5, { scope: globalScope }, 'withAccountsAndUsersAndManagedGroups' ); @@ -51,8 +51,43 @@ export default function (server) { server.createList('group', 1, { scope: globalScope }, 'withMembers'); server.createList('group', 5, { scope: orgScope }, 'withMembers'); // Role - server.createList('role', 1, { scope: globalScope }, 'withPrincipals'); - server.createList('role', 5, { scope: orgScope }, 'withPrincipals'); + const globalScopeRoles = server.createList( + 'role', + 1, + { scope: globalScope }, + 'withPrincipals' + ); + //create managed groups for role/principals in globalScope + globalScopeRoles.forEach((role) => { + const { scope } = role; + const oidcAuthMethod = globalAuthMethods.filter( + (auth) => auth.type === 'oidc' + )[0]; + const managedGroups = server.createList('managed-group', 2, { + scope, + authMethodId: oidcAuthMethod.id, + }); + role.update({ managedGroups }); + }); + + const orgScopeRoles = server.createList( + 'role', + 5, + { scope: orgScope }, + 'withPrincipals' + ); + //create managed groups for role/principals in orgScope + orgScopeRoles.forEach((role) => { + const { scope } = role; + const oidcAuthMethod = orgAuthMethods.filter( + (auth) => auth.type === 'oidc' + )[0]; + const managedGroups = server.createList('managed-group', 2, { + scope, + authMethodId: oidcAuthMethod.id, + }); + role.update({ managedGroups }); + }); // Other resources server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => { diff --git a/addons/api/mirage/serializers/role.js b/addons/api/mirage/serializers/role.js index db04fe328..14b8d82ac 100644 --- a/addons/api/mirage/serializers/role.js +++ b/addons/api/mirage/serializers/role.js @@ -18,7 +18,19 @@ export default ApplicationSerializer.extend({ id: group.id, type: 'group', })); - json.principals = [...serializedUsers, ...serializedGroups]; + const serializedManagedGroups = model.managedGroups.models.map( + (managedGroup) => ({ + scope_id: model.scope.id, + id: managedGroup.id, + type: 'managed group', + }) + ); + json.principals = [ + ...serializedUsers, + ...serializedGroups, + ...serializedManagedGroups, + ]; + if (!json.principals.length) delete json.principals; // default grant scope diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 035a8e4ae..eacbb9877 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -271,6 +271,7 @@ role: types: user: User group: Group + managed-group: Managed Group actions: add-principals: Add Principals grant: diff --git a/ui/admin/tests/acceptance/roles/principals-test.js b/ui/admin/tests/acceptance/roles/principals-test.js index 5a6ad8b00..9d6c88b86 100644 --- a/ui/admin/tests/acceptance/roles/principals-test.js +++ b/ui/admin/tests/acceptance/roles/principals-test.js @@ -46,7 +46,8 @@ module('Acceptance | roles | principals', function (hooks) { ); principalsCount = this.server.db.roles[0].userIds.length + - this.server.db.roles[0].groupIds.length; + this.server.db.roles[0].groupIds.length + + this.server.db.roles[0].managedGroupIds.length; urls.roles = `/scopes/${instances.scopes.org.id}/roles`; urls.role = `${urls.roles}/${instances.role.id}`; urls.rolePrincipals = `${urls.role}/principals`; @@ -111,7 +112,7 @@ module('Acceptance | roles | principals', function (hooks) { test('select and save principals to add', async function (assert) { assert.expect(3); - instances.role.update({ userIds: [], groupIds: [] }); + instances.role.update({ userIds: [], groupIds: [], managedGroupIds: [] }); await visit(urls.rolePrincipals); assert.strictEqual(findAll('tbody tr').length, 0); await click('.rose-layout-page-actions a'); @@ -154,7 +155,7 @@ module('Acceptance | roles | principals', function (hooks) { } ); }); - instances.role.update({ userIds: [], groupIds: [] }); + instances.role.update({ userIds: [], groupIds: [], managedGroupIds: [] }); await visit(urls.addPrincipals); await click('tbody label'); await click('form [type="submit"]'); From f2c0458fb5cb8d42dcd8a6a63e3b4b52259b9049 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Thu, 22 Dec 2022 14:09:56 -0800 Subject: [PATCH 25/25] Add model support for managed groups (#1544) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 streamline managed groups getter on role --- addons/api/addon/models/role.js | 119 ++++++++++++++++++ addons/api/tests/unit/models/role-test.js | 7 +- .../scopes/scope/roles/role/principals.js | 48 ++----- 3 files changed, 134 insertions(+), 40 deletions(-) diff --git a/addons/api/addon/models/role.js b/addons/api/addon/models/role.js index 4f2efa720..226c27b2f 100644 --- a/addons/api/addon/models/role.js +++ b/addons/api/addon/models/role.js @@ -1,11 +1,13 @@ import GeneratedRoleModel from '../generated/models/role'; import { attr } from '@ember-data/model'; import { inject as service } from '@ember/service'; +import { all, resolve } from 'rsvp'; export default class RoleModel extends GeneratedRoleModel { // =services @service store; + @service resourceFilterStore; // =attributes @@ -37,6 +39,123 @@ export default class RoleModel extends GeneratedRoleModel { : null; } + /** + * A list of IDs for principals of type `user`. + * @type {string[]} + */ + get userIDs() { + return this.principals + .filter(({ type }) => type === 'user') + .map(({ principal_id }) => principal_id); + } + + /** + * A list of IDs for principals of type `group`. + * @type {string[]} + */ + get groupIDs() { + return this.principals + .filter(({ type }) => type === 'group') + .map(({ principal_id }) => principal_id); + } + + /** + * A list of IDs for principals of type `managed group`. + * @type {string[]} + */ + get managedGroupIDs() { + return this.principals + .filter(({ type }) => type === 'managed group') + .map(({ principal_id }) => principal_id); + } + + /** + * A promise that resolves to an array of User instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[UserModel]} + */ + get users() { + const ids = this.userIDs; + + // Role has prinicipal IDs, + // return a promise which resolves model instances for those IDs + if (ids?.length) { + return this.resourceFilterStore + .queryBy('user', { id: ids }, { scope_id: 'global', recursive: true }) + .then((models) => models.map((model) => model)); + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + + /** + * A promise that resolves to an array of Group instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[GroupModel]} + */ + get groups() { + const ids = this.groupIDs; + + // Role has prinicipal IDs, + // return a promise which resolves model instances for those IDs + if (ids?.length) { + return this.resourceFilterStore + .queryBy('group', { id: ids }, { scope_id: 'global', recursive: true }) + .then((models) => models.map((model) => model)); + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + + /** + * A promise that resolves to an array of ManagedGroup instances. + * When calling this getter, be sure to await resolution + * before interacting with the results. + * @type {Promise[ManagedGroupModel]} + */ + get managedGroups() { + const ids = this.managedGroupIDs; + + // Role has prinicipal IDs + // return a promise which resolves model instances for those IDs + if (ids?.length) { + // Collect all auth methods. + const authMethods = this.resourceFilterStore.queryBy( + 'auth-method', + { type: 'oidc' }, + { scope_id: 'global', recursive: true } + ); + + // For each auth method, query all managed groups with IDs + // that match the role's manage group principal IDs. + const managedGroups = authMethods + .then((methods) => + all( + methods.map(({ id: auth_method_id }) => + this.resourceFilterStore + .queryBy('managed-group', { id: ids }, { auth_method_id }) + .then((models) => models.map((model) => model)) + ) + ) + ) + // The result is an array of arrays of model instances (grouped by + // auth methods), so these must be flattened. + .then((grouped) => grouped.flat()); + + return managedGroups; + } + + // No principal IDs, + // return a promise resolving to an empty array + return resolve([]); + } + // =methods /** diff --git a/addons/api/tests/unit/models/role-test.js b/addons/api/tests/unit/models/role-test.js index 9d64e9ac2..fc0c5d8e8 100644 --- a/addons/api/tests/unit/models/role-test.js +++ b/addons/api/tests/unit/models/role-test.js @@ -64,6 +64,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { @@ -84,7 +85,7 @@ module('Unit | Model | role', function (hooks) { (schema, request) => { const body = JSON.parse(request.requestBody); assert.deepEqual(body, { - principal_ids: ['1', '3'], + principal_ids: ['1', '3', '5'], version: 1, }); return { id: '123abc' }; @@ -101,6 +102,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { @@ -111,7 +113,7 @@ module('Unit | Model | role', function (hooks) { }, }); const model = store.peekRecord('role', '123abc'); - await model.removePrincipals(['1', '3']); + await model.removePrincipals(['1', '3', '5']); }); test('it has a `removePrincipal` method that deletes a single principal using `removePrincipals` method', async function (assert) { @@ -138,6 +140,7 @@ module('Unit | Model | role', function (hooks) { principals: [ { id: '1', type: 'user' }, { id: '3', type: 'group' }, + { id: '5', type: 'managed group' }, ], version: 1, scope: { diff --git a/ui/admin/app/routes/scopes/scope/roles/role/principals.js b/ui/admin/app/routes/scopes/scope/roles/role/principals.js index 25b9cbb64..4977dac20 100644 --- a/ui/admin/app/routes/scopes/scope/roles/role/principals.js +++ b/ui/admin/app/routes/scopes/scope/roles/role/principals.js @@ -8,60 +8,32 @@ import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeRolesRolePrincipalsRoute extends Route { // =services @service intl; - @service resourceFilterStore; // =methods /** - * Returns users and groups associated with this role. + * Returns users, groups and managed-groups associated with this role. * @param {object} params - * @return {Promise{RoleModel}} + * @return {Promise{role, principals}} */ async model() { const role = this.modelFor('scopes.scope.roles.role'); + // Fetch user and group principals. + const users = await role.users; + const groups = await role.groups; + const managedGroups = await role.managedGroups; + // Merge polymorphic principals. + const principals = [...users, ...groups, ...managedGroups]; - // Gather user and group IDs as separate arrays, since these - // will be queried in separate API queries. - const userIDs = role.principals - .filter(({ type }) => type === 'user') - .map(({ principal_id }) => principal_id); - const groupIDs = role.principals - .filter(({ type }) => type === 'group') - .map(({ principal_id }) => principal_id); - - // Query for users. - const users = userIDs?.length - ? ( - await this.resourceFilterStore.queryBy( - 'user', - { id: userIDs }, - { scope_id: 'global', recursive: true } - ) - ).map((model) => model) - : []; - // Query for groups. - const groups = groupIDs?.length - ? ( - await this.resourceFilterStore.queryBy( - 'group', - { id: groupIDs }, - { scope_id: 'global', recursive: true } - ) - ).map((model) => model) - : []; - - return { - role, - principals: users.concat(groups), - }; + return { role, principals }; } // =actions /** * Remove a principal from the current role and redirect to principals index. - * @param {UserModel, GroupModel} principal + * @param {UserModel, GroupModel, ManagedGroupModel} principal */ @action @loading