From 45fc58fe757b53980848389d3b0bdfb2ec759ec8 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Sun, 18 Jul 2021 14:31:45 -0400 Subject: [PATCH 01/13] feat: Init credential library creation --- addons/api/addon/models/credential-library.js | 13 ++- addons/api/mirage/config.js | 10 +- addons/core/translations/form/en-us.yaml | 9 ++ addons/core/translations/resources/en-us.yaml | 2 + .../form/credential-library/index.hbs | 105 ++++++++++++++++++ .../form/credential-library/index.js | 20 ++++ ui/admin/app/router.js | 4 +- .../credential-stores/credential-store.js | 7 +- .../credential-store/credential-libraries.js | 37 ++++++ .../credential-libraries/new.js | 45 ++++++++ .../credential-stores/credential-store.hbs | 2 +- .../credential-store/-actions.hbs | 12 ++ .../credential-libraries/new.hbs | 7 ++ .../credential-libraries/new/-header.hbs | 3 + .../form/credential-library/index-test.js | 29 +++++ .../credential-libraries/new-test.js | 16 +++ 16 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 ui/admin/app/components/form/credential-library/index.hbs create mode 100644 ui/admin/app/components/form/credential-library/index.js create mode 100644 ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs create mode 100644 ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs create mode 100644 ui/admin/tests/integration/components/form/credential-library/index-test.js create mode 100644 ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js diff --git a/addons/api/addon/models/credential-library.js b/addons/api/addon/models/credential-library.js index c95b9f73e..4ca321a18 100644 --- a/addons/api/addon/models/credential-library.js +++ b/addons/api/addon/models/credential-library.js @@ -2,13 +2,24 @@ import GeneratedCredentialLibraryModel from '../generated/models/credential-libr import { attr } from '@ember-data/model'; import { fragment } from 'ember-data-model-fragments/attributes'; +/** + * Enum options for credential library. + */ +export const options = { + http_methods: ['GET', 'POST'], +}; + export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel { // =error attributes // These attributes exist solely to capture errors on nested fields. // See the application adapter's error normalization method for // more information. - @attr('string', { readOnly: true }) attributes_http_method; + @attr('string', { + readOnly: true, + defaultValue: options.http_methods[0], + }) + attributes_http_method; @attr('string', { readOnly: true }) attributes_http_request_body; @attr('string', { readOnly: true }) attributes_path; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 1fd364182..a45cd1bab 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -478,7 +478,15 @@ export default function () { ) => credentialLibraries.where({ credentialStoreId }) ); this.get('/credential-libraries/:id'); - this.post('/credential-libraries/:id'); + this.post( + '/credential-libraries', + function ({ credentialStores, credentialLibraries }) { + const attrs = this.normalizedRequestAttrs(); + const credentialStore = credentialStores.find(attrs.credentialStoreId); + attrs.scopeId = credentialStore.scope.id; + return credentialLibraries.create(attrs); + } + ); this.del('/credential-libraries/:id'); this.patch('/credential-libraries/:id'); diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index cb7b69832..b59b55b91 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -115,3 +115,12 @@ value: callback_url: label: Callback URL help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration. +vault_path: + label: Vault Path + help: The path in Vault to request credentials from. +http_method: + label: HTTP Method + help: The HTTP method the library uses when requesting credentials from Vault. +http_request_body: + label: HTTP Request Body + help: The body of the HTTP request the library sends to Vault when requesting credentials. diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index cc9fed978..9468e3500 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -275,6 +275,8 @@ credential-library: title: Credential Library title_plural: Credential Libraries description: A credential library is a resource that provides credentials. + actions: + create: New Credential Library messages: none: title: No Credential Libraries Available diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs new file mode 100644 index 000000000..f5bd6c4eb --- /dev/null +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -0,0 +1,105 @@ + + + {{#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.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#each this.httpMethods as |httpMethod|}} + + {{/each}} + + + {{#if this.isHttpRequestBodyAllowed}} + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + {{/if}} + + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/index.js b/ui/admin/app/components/form/credential-library/index.js new file mode 100644 index 000000000..69427c13b --- /dev/null +++ b/ui/admin/app/components/form/credential-library/index.js @@ -0,0 +1,20 @@ +import Component from '@glimmer/component'; +import { options } from 'api/models/credential-library'; +import { computed } from '@ember/object'; + +export default class FormCredentialLibraryIndexComponent extends Component { + // =properties + /** + * @type {object} + */ + httpMethods = options.http_methods; + + /** + * Only allow HTTP request body field if http_method is set to POST. + * @type {boolean} + */ + @computed('args.model.attributes_http_method') + get isHttpRequestBodyAllowed() { + return this.args.model.attributes_http_method?.match(/post/i); + } +} diff --git a/ui/admin/app/router.js b/ui/admin/app/router.js index 4012791b2..3c359e268 100644 --- a/ui/admin/app/router.js +++ b/ui/admin/app/router.js @@ -91,7 +91,9 @@ Router.map(function () { 'credential-store', { path: ':credential_store_id' }, function () { - this.route('credential-libraries', function () {}); + this.route('credential-libraries', function () { + this.route('new'); + }); } ); this.route('new'); diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js index 307c11079..fa5b56baf 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store.js @@ -10,7 +10,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Rou } /** - * Render credential store header and navigation page sections. + * Render credential store header, navigation, and actions page sections. * @override */ renderTemplate() { @@ -25,5 +25,10 @@ export default class ScopesScopeCredentialStoresCredentialStoreRoute extends Rou into: 'scopes/scope/credential-stores/credential-store', outlet: 'navigation', }); + + this.render('scopes/scope/credential-stores/credential-store/-actions', { + into: 'scopes/scope/credential-stores/credential-store', + outlet: 'actions', + }); } } diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js index 7c8ade755..fa54e9691 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js @@ -1,4 +1,7 @@ import Route from '@ember/routing/route'; +import { action } from '@ember/object'; +import loading from 'ember-loading/decorator'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route { // =methods @@ -13,4 +16,38 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari ); return this.store.query('credential-library', { credential_store_id }); } + + // =actions + + /** + * Rollback changes on a credential library. + * @param {CredentialLibraryModel} credentialLibrary + */ + @action + cancel(credentialLibrary) { + const { isNew } = credentialLibrary; + credentialLibrary.rollbackAttributes(); + if (isNew) + this.transitionTo( + 'scopes.scope.credential-stores.credential-store.credential-libraries' + ); + } + + /** + * Handle save of a credential library + * @param {CredentialLibraryModel} credentialLibrary + */ + @action + @loading + @notifyError(({ message }) => message) + @notifySuccess(({ isNew }) => + isNew ? 'notifications.create-success' : 'notifications.save-success' + ) + async save(credentialLibrary) { + await credentialLibrary.save(); + await this.transitionTo( + 'scopes.scope.credential-stores.credential-store.credential-libraries' + ); + this.refresh(); + } } diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js new file mode 100644 index 000000000..d83109dda --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new.js @@ -0,0 +1,45 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewRoute extends Route { + // =methods + + /** + * Creates a new unsaved credential library in current credential store. + * @return {CredentialLibraryModel} + */ + model() { + const { id: credential_store_id } = this.modelFor( + 'scopes.scope.credential-stores.credential-store' + ); + return this.store.createRecord('credential-library', { + credential_store_id, + }); + } + + /** + * Renders new credential library specific templates for header, navigation, + * and action page sections. + * @override + */ + renderTemplate() { + super.renderTemplate(...arguments); + + this.render( + 'scopes/scope/credential-stores/credential-store/credential-libraries/new/-header', + { + into: 'scopes/scope/credential-stores/credential-store', + outlet: 'header', + } + ); + + this.render('_empty', { + into: 'scopes/scope/credential-stores/credential-store', + outlet: 'navigation', + }); + + this.render('_empty', { + into: 'scopes/scope/credential-stores/credential-store', + outlet: 'actions', + }); + } +} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs index a6ecd7f81..efc46641a 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store.hbs @@ -15,7 +15,7 @@ - {{!-- {{outlet 'actions'}} --}} + {{outlet 'actions'}} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs new file mode 100644 index 000000000..587d095db --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs @@ -0,0 +1,12 @@ + + + {{t 'resources.credential-library.actions.create'}} + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs new file mode 100644 index 000000000..5d03e5b9f --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new.hbs @@ -0,0 +1,7 @@ +{{page-title (t 'titles.new')}} + + \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs new file mode 100644 index 000000000..51755b136 --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/new/-header.hbs @@ -0,0 +1,3 @@ +

+ {{t 'titles.new'}} +

\ No newline at end of file diff --git a/ui/admin/tests/integration/components/form/credential-library/index-test.js b/ui/admin/tests/integration/components/form/credential-library/index-test.js new file mode 100644 index 000000000..e0d24a8c2 --- /dev/null +++ b/ui/admin/tests/integration/components/form/credential-library/index-test.js @@ -0,0 +1,29 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; + +module( + 'Integration | Component | form/credential-library/index', + function (hooks) { + setupRenderingTest(hooks); + + test('it renders', async function (assert) { + // Set any properties with this.set('myProperty', 'value'); + // Handle any actions with this.set('myAction', function(val) { ... }); + + await render(hbs``); + + assert.equal(this.element.textContent.trim(), ''); + + // Template block usage: + await render(hbs` + + template block text + + `); + + assert.equal(this.element.textContent.trim(), 'template block text'); + }); + } +); diff --git a/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js new file mode 100644 index 000000000..3c4efdd66 --- /dev/null +++ b/ui/admin/tests/unit/routes/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js @@ -0,0 +1,16 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Route | scopes/scope/credential-stores/credential-store/credential-libraries/new', + function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let route = this.owner.lookup( + 'route:scopes/scope/credential-stores/credential-store/credential-libraries/new' + ); + assert.ok(route); + }); + } +); From 98e14a642fe13c7e514d49b2d8b810f144da8b19 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 19 Jul 2021 12:05:40 -0400 Subject: [PATCH 02/13] fix: Treat credential library attributes as an object --- addons/api/mirage/config.js | 1 + .../form/credential-library/index.hbs | 8 ++--- .../form/credential-library/index.js | 4 +-- .../credential-libraries/index.hbs | 6 ++++ .../form/credential-library/index-test.js | 29 ------------------- 5 files changed, 13 insertions(+), 35 deletions(-) delete mode 100644 ui/admin/tests/integration/components/form/credential-library/index-test.js diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index a45cd1bab..5d9ddaf8e 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -484,6 +484,7 @@ export default function () { const attrs = this.normalizedRequestAttrs(); const credentialStore = credentialStores.find(attrs.credentialStoreId); attrs.scopeId = credentialStore.scope.id; + console.log(attrs); return credentialLibraries.create(attrs); } ); diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index f5bd6c4eb..35aa5c339 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -47,7 +47,7 @@ {{#each this.httpMethods as |httpMethod|}} @@ -78,7 +78,7 @@ {{t 'resources.credential-library.messages.none.description'}} + + + {{t 'titles.new'}} + diff --git a/ui/admin/tests/integration/components/form/credential-library/index-test.js b/ui/admin/tests/integration/components/form/credential-library/index-test.js deleted file mode 100644 index e0d24a8c2..000000000 --- a/ui/admin/tests/integration/components/form/credential-library/index-test.js +++ /dev/null @@ -1,29 +0,0 @@ -import { module, test } from 'qunit'; -import { setupRenderingTest } from 'ember-qunit'; -import { render } from '@ember/test-helpers'; -import { hbs } from 'ember-cli-htmlbars'; - -module( - 'Integration | Component | form/credential-library/index', - function (hooks) { - setupRenderingTest(hooks); - - test('it renders', async function (assert) { - // Set any properties with this.set('myProperty', 'value'); - // Handle any actions with this.set('myAction', function(val) { ... }); - - await render(hbs``); - - assert.equal(this.element.textContent.trim(), ''); - - // Template block usage: - await render(hbs` - - template block text - - `); - - assert.equal(this.element.textContent.trim(), 'template block text'); - }); - } -); From 59818fa24d8d53338450f49e92839a0cfdf8e11f Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Mon, 19 Jul 2021 13:13:35 -0400 Subject: [PATCH 03/13] feat: Add credential libraries breadcrumbs --- .../credential-store/credential-libraries.js | 18 ++++++++++++++++++ .../credential-libraries/new.js | 18 ++++++++++++++++++ .../credential-libraries-test.js | 17 +++++++++++++++++ .../credential-libraries/new-test.js | 17 +++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries.js create mode 100644 ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new.js create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries-test.js create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries.js new file mode 100644 index 000000000..fe9084ea7 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries.js @@ -0,0 +1,18 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Translated credential libraries breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('resources.credential-library.title_plural'); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new.js b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new.js new file mode 100644 index 000000000..ff4053f92 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new.js @@ -0,0 +1,18 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Translated new credential libraries breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t('titles.new'); + } +} diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries-test.js new file mode 100644 index 000000000..944329525 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries', + function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/credential-stores/credential-store/credential-libraries' + ); + assert.ok(controller); + }); + } +); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js new file mode 100644 index 000000000..2dce4303a --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/credential-stores/credential-store/credential-libraries/new-test.js @@ -0,0 +1,17 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module( + 'Unit | Controller | scopes/scope/credential-stores/credential-store/credential-libraries/new', + function (hooks) { + setupTest(hooks); + + // TODO: Replace this with your real tests. + test('it exists', function (assert) { + let controller = this.owner.lookup( + 'controller:scopes/scope/credential-stores/credential-store/credential-libraries/new' + ); + assert.ok(controller); + }); + } +); From dd96423ead34e4793b636d1c1015a0743b7b18fc Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Tue, 20 Jul 2021 09:32:41 -0400 Subject: [PATCH 04/13] fix: Disable credential library request body in form --- addons/core/translations/form/en-us.yaml | 2 +- .../form/credential-library/index.hbs | 42 +++++++++---------- .../form/credential-library/index.js | 8 ++-- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index b59b55b91..171011a4a 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -123,4 +123,4 @@ http_method: help: The HTTP method the library uses when requesting credentials from Vault. http_request_body: label: HTTP Request Body - help: The body of the HTTP request the library sends to Vault when requesting credentials. + help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when http_method is set to POST. diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index 35aa5c339..d45f78b58 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -31,7 +31,7 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} + @disabled={{@model.isSaving}} readonly={{false}} as |field| > @@ -74,27 +74,25 @@ {{/each}} - {{#if this.isHttpRequestBodyAllowed}} - - {{#if @model.errors.description}} - - {{#each @model.errors.description as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - {{/if}} + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + Date: Tue, 20 Jul 2021 16:09:59 -0400 Subject: [PATCH 05/13] feat: API: Load credential libraries associated to a target --- .../models/fragment-credential-library.js | 11 ++++++++ addons/api/addon/models/target.js | 22 ++++++++++++++++ .../fragment-credential-library.js | 16 ++++++++++++ addons/api/addon/serializers/target.js | 26 ++++++++++++++++++- .../app/models/fragment-credential-library.js | 1 + .../fragment-credential-library.js | 1 + addons/api/mirage/factories/target.js | 18 +++++++++---- addons/api/mirage/models/target.js | 1 + addons/api/mirage/scenarios/default.js | 2 +- addons/api/mirage/serializers/target.js | 3 +++ .../fragment-credential-library-test.js | 13 ++++++++++ .../fragment-credential-library-test.js | 23 ++++++++++++++++ 12 files changed, 130 insertions(+), 7 deletions(-) create mode 100644 addons/api/addon/models/fragment-credential-library.js create mode 100644 addons/api/addon/serializers/fragment-credential-library.js create mode 100644 addons/api/app/models/fragment-credential-library.js create mode 100644 addons/api/app/serializers/fragment-credential-library.js create mode 100644 addons/api/tests/unit/models/fragment-credential-library-test.js create mode 100644 addons/api/tests/unit/serializers/fragment-credential-library-test.js diff --git a/addons/api/addon/models/fragment-credential-library.js b/addons/api/addon/models/fragment-credential-library.js new file mode 100644 index 000000000..384ffef1e --- /dev/null +++ b/addons/api/addon/models/fragment-credential-library.js @@ -0,0 +1,11 @@ +import Fragment from 'ember-data-model-fragments/fragment'; +import { attr } from '@ember-data/model'; + +export default class FragmentCredentialLibraryModel extends Fragment { + // =attributes + + @attr('string', { + description: 'ID of the related credential library', + }) + credential_library_id; +} diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index a00c3b182..3788e595f 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -20,6 +20,15 @@ export default class TargetModel extends GeneratedTargetModel { }) host_sets; + /** + * @type {[FragmentCredentialLibrary]} + */ + @fragmentArray('fragment-credential-library', { + readOnly: true, + emptyArrayIfMissing: true, + }) + credential_libraries; + /** * An array of resolved host set and host catalog instances. Model instances * must already be loaded into the store (this method will not load unloaded @@ -65,6 +74,19 @@ export default class TargetModel extends GeneratedTargetModel { return Boolean(pendingOrActiveSessions.length); } + /** + * An array of resolved credential library instances. Model instances + * must already be loaded into the store (this method will not load unloaded + * instances). Unresolvable instances are excluded from the array. + * @type {[CredentialLibraryModel]} + */ + @computed('credential_libraries.[]', 'store') + get credentialLibraries() { + return this.credential_libraries + .map((id) => this.store.peekRecord('credential-library', id)) + .filter(Boolean); + } + // =methods /** diff --git a/addons/api/addon/serializers/fragment-credential-library.js b/addons/api/addon/serializers/fragment-credential-library.js new file mode 100644 index 000000000..9f3d01c81 --- /dev/null +++ b/addons/api/addon/serializers/fragment-credential-library.js @@ -0,0 +1,16 @@ +import JSONSerializer from '@ember-data/serializer/json'; + +export default class FragmentCredentialLibrarySerializer extends JSONSerializer { + // =methods + + /** + * Renames `id` to `credential_library_id` because fragments may not have IDs. + * Otherwise normalizes as normal. + * @param {ModelClass} typeClass + * @param {object} resourceHash + * @return {object} + */ + normalize(typeClass, { id: credential_library_id, ...obj }) { + return super.normalize(typeClass, { credential_library_id, ...obj }); + } +} diff --git a/addons/api/addon/serializers/target.js b/addons/api/addon/serializers/target.js index 4c6745b89..c287aca46 100644 --- a/addons/api/addon/serializers/target.js +++ b/addons/api/addon/serializers/target.js @@ -6,7 +6,11 @@ export default class TargetSerializer extends ApplicationSerializer { /** * If `adapterOptions.hostSets` is set to an array of host set models, * the resulting target serialization should include **only host sets** - * and the version. Normally, host sets are not serialized. + * and the version. + * If `adapterOptions.credentialLibraries` is set to an array of + * credential library models, the resulting target serialization should + * include **only credential libraries** and the version. + * Normally, neither host sets or credential libraries are not serialized. * @override * @param {Snapshot} snapshot * @return {object} @@ -16,6 +20,12 @@ export default class TargetSerializer extends ApplicationSerializer { const hostSetIDs = snapshot?.adapterOptions?.hostSetIDs; if (hostSetIDs) serialized = this.serializeWithHostSets(snapshot, hostSetIDs); + const credentialLibraryIDs = snapshot?.adapterOptions?.credentialLibraryIDs; + if (credentialLibraryIDs) + serialized = this.serializeWithCredentialLibraries( + snapshot, + credentialLibraryIDs + ); return serialized; } @@ -32,4 +42,18 @@ export default class TargetSerializer extends ApplicationSerializer { host_set_ids: hostSetIDs, }; } + + /** + * Returns a payload containing only the credential_library_ids array using IDs + * passed into the function (rather than existing credential libraries on the model). + * @param {Snapshot} snapshot + * @param {[string]} credentialLibraryIDs + * @return {object} + */ + serializeWithCredentialLibraries(snapshot, credentialLibraryIDs) { + return { + version: snapshot.attr('version'), + application_credential_library_ids: credentialLibraryIDs, + }; + } } diff --git a/addons/api/app/models/fragment-credential-library.js b/addons/api/app/models/fragment-credential-library.js new file mode 100644 index 000000000..4cee0ae38 --- /dev/null +++ b/addons/api/app/models/fragment-credential-library.js @@ -0,0 +1 @@ +export { default } from 'api/models/fragment-credential-library'; diff --git a/addons/api/app/serializers/fragment-credential-library.js b/addons/api/app/serializers/fragment-credential-library.js new file mode 100644 index 000000000..b60ecfef5 --- /dev/null +++ b/addons/api/app/serializers/fragment-credential-library.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/fragment-credential-library'; diff --git a/addons/api/mirage/factories/target.js b/addons/api/mirage/factories/target.js index dc1c97d00..8ae03edbe 100644 --- a/addons/api/mirage/factories/target.js +++ b/addons/api/mirage/factories/target.js @@ -7,7 +7,7 @@ const hostSetChance = 0.3; export default factory.extend({ /** - * -1 means "unlimited" and we want to generate these on occassion. + * -1 means "unlimited" and we want to generate these on occasion. */ session_connection_limit: () => random.arrayElement([-1, datatype.number()]), @@ -25,11 +25,11 @@ export default factory.extend({ }, /** - * Randomly selects existing host sets to assign to target. + * Randomly selects existing host sets and credential libraries to assign to target. */ - withRandomHostSets: trait({ + withAssociations: trait({ afterCreate(target, server) { - let randomlySelectedHostSets; + let randomlySelectedHostSets, randomlySelectedCredentialLibraries; randomlySelectedHostSets = server.schema.hostSets // BLERG: fun fact, for no reason at all, the element passed // into a where function is not a full model instance, as you might @@ -38,7 +38,15 @@ export default factory.extend({ // the result set of `where` _is a collection of model instances_. .where((hostSet) => hostSet.scopeId === target.scope.id) .models.filter(() => randomBoolean(hostSetChance)); - target.update({ hostSets: randomlySelectedHostSets }); + + randomlySelectedCredentialLibraries = server.schema.credentialLibraries + .where((credentialLibrary) => credentialLibrary.scopeId === target.scope.id) + .models.filter(() => randomBoolean()); + + target.update({ + hostSets: randomlySelectedHostSets, + credentialLibraries: randomlySelectedCredentialLibraries, + }); }, }), }); diff --git a/addons/api/mirage/models/target.js b/addons/api/mirage/models/target.js index 0f03cb6ef..389e255d2 100644 --- a/addons/api/mirage/models/target.js +++ b/addons/api/mirage/models/target.js @@ -3,4 +3,5 @@ import { Model, belongsTo, hasMany } from 'ember-cli-mirage'; export default Model.extend({ scope: belongsTo(), hostSets: hasMany(), + credentialLibraries: hasMany(), }); diff --git a/addons/api/mirage/scenarios/default.js b/addons/api/mirage/scenarios/default.js index fc5931908..d448a443b 100644 --- a/addons/api/mirage/scenarios/default.js +++ b/addons/api/mirage/scenarios/default.js @@ -47,8 +47,8 @@ export default function (server) { // Other resources server.schema.scopes.where({ type: 'project' }).models.forEach((scope) => { server.createList('host-catalog', 2, { scope }, 'withChildren'); - server.createList('target', 2, { scope }, 'withRandomHostSets'); server.createList('session', 4, { scope }, 'withAssociations'); server.createList('credential-store', 3, { scope }, 'withAssociations'); + server.createList('target', 2, { scope }, 'withAssociations'); }); } diff --git a/addons/api/mirage/serializers/target.js b/addons/api/mirage/serializers/target.js index 006388773..c23330189 100644 --- a/addons/api/mirage/serializers/target.js +++ b/addons/api/mirage/serializers/target.js @@ -23,6 +23,9 @@ export default ApplicationSerializer.extend({ return { id: host_set_id, host_catalog_id }; }); } + if (model.credentialLibraryIds?.length) { + json.credential_libraries = model.credentialLibraryIds; + } return json; }, }); diff --git a/addons/api/tests/unit/models/fragment-credential-library-test.js b/addons/api/tests/unit/models/fragment-credential-library-test.js new file mode 100644 index 000000000..f5014569e --- /dev/null +++ b/addons/api/tests/unit/models/fragment-credential-library-test.js @@ -0,0 +1,13 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Model | fragment credential library', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let model = store.createRecord('fragment-credential-library', {}); + assert.ok(model); + }); +}); diff --git a/addons/api/tests/unit/serializers/fragment-credential-library-test.js b/addons/api/tests/unit/serializers/fragment-credential-library-test.js new file mode 100644 index 000000000..719d97955 --- /dev/null +++ b/addons/api/tests/unit/serializers/fragment-credential-library-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | fragment credential library', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('fragment-credential-library'); + + assert.ok(serializer); + }); + + test('it serializes records', function (assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('fragment-credential-library', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); From 28a4a7304e06d7d6aba520da611421958f1cb3ea Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Wed, 21 Jul 2021 15:45:45 -0400 Subject: [PATCH 06/13] feat: Add credential library add and remove methods to target --- .../models/fragment-credential-library.js | 11 -- addons/api/addon/models/target.js | 100 +++++++++-- .../fragment-credential-library.js | 16 -- .../app/models/fragment-credential-library.js | 1 - .../fragment-credential-library.js | 1 - addons/api/mirage/config.js | 20 ++- addons/api/mirage/factories/target.js | 4 +- addons/api/mirage/serializers/target.js | 2 +- .../fragment-credential-library-test.js | 13 -- addons/api/tests/unit/models/target-test.js | 158 ++++++++++++++++++ .../unit/serializers/application-test.js | 1 + .../fragment-credential-library-test.js | 23 --- .../api/tests/unit/serializers/target-test.js | 29 +++- ui/admin/tests/acceptance/sessions-test.js | 2 +- ui/desktop/tests/acceptance/origin-test.js | 2 +- ui/desktop/tests/acceptance/projects-test.js | 2 +- .../acceptance/projects/sessions-test.js | 2 +- .../tests/acceptance/projects/targets-test.js | 2 +- .../projects/targets/sessions-test.js | 2 +- ui/desktop/tests/acceptance/scopes-test.js | 2 +- ui/desktop/tests/acceptance/targets-test.js | 2 +- 21 files changed, 301 insertions(+), 94 deletions(-) delete mode 100644 addons/api/addon/models/fragment-credential-library.js delete mode 100644 addons/api/addon/serializers/fragment-credential-library.js delete mode 100644 addons/api/app/models/fragment-credential-library.js delete mode 100644 addons/api/app/serializers/fragment-credential-library.js delete mode 100644 addons/api/tests/unit/models/fragment-credential-library-test.js delete mode 100644 addons/api/tests/unit/serializers/fragment-credential-library-test.js diff --git a/addons/api/addon/models/fragment-credential-library.js b/addons/api/addon/models/fragment-credential-library.js deleted file mode 100644 index 384ffef1e..000000000 --- a/addons/api/addon/models/fragment-credential-library.js +++ /dev/null @@ -1,11 +0,0 @@ -import Fragment from 'ember-data-model-fragments/fragment'; -import { attr } from '@ember-data/model'; - -export default class FragmentCredentialLibraryModel extends Fragment { - // =attributes - - @attr('string', { - description: 'ID of the related credential library', - }) - credential_library_id; -} diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 3788e595f..d60596465 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -21,13 +21,14 @@ export default class TargetModel extends GeneratedTargetModel { host_sets; /** - * @type {[FragmentCredentialLibrary]} + * Credential library ids are read only and can be + * persisted via a dedicated call to `addCredentialLibraries()`. */ - @fragmentArray('fragment-credential-library', { + @fragmentArray('fragment-string', { readOnly: true, emptyArrayIfMissing: true, }) - credential_libraries; + application_credential_library_ids; /** * An array of resolved host set and host catalog instances. Model instances @@ -45,6 +46,24 @@ export default class TargetModel extends GeneratedTargetModel { .filter((hostSetRef) => hostSetRef.model !== null); } + /** + * An array of resolved credential library instances. Model instances + * must already be loaded into the store (this method will not load unloaded + * instances). Unresolvable instances are excluded from the array. + * @type {[CredentialLibraryModel]} + */ + @computed('application_credential_library_ids.[]', 'store') + get credentialLibraries() { + return this.application_credential_library_ids + .map((credential_library_fragment) => + this.store.peekRecord( + 'credential-library', + credential_library_fragment.value + ) + ) + .filter(Boolean); + } + /** * Sessions associated with this target (but only already loaded sessions). * @type {SessionModel[]} @@ -74,19 +93,6 @@ export default class TargetModel extends GeneratedTargetModel { return Boolean(pendingOrActiveSessions.length); } - /** - * An array of resolved credential library instances. Model instances - * must already be loaded into the store (this method will not load unloaded - * instances). Unresolvable instances are excluded from the array. - * @type {[CredentialLibraryModel]} - */ - @computed('credential_libraries.[]', 'store') - get credentialLibraries() { - return this.credential_libraries - .map((id) => this.store.peekRecord('credential-library', id)) - .filter(Boolean); - } - // =methods /** @@ -144,4 +150,66 @@ export default class TargetModel extends GeneratedTargetModel { removeHostSet(hostSetID, options) { return this.removeHostSets([hostSetID], options); } + + /** + * Adds credential libraries via the `add-credential-libraries` method. + * See serializer and adapter for more information. + * @param {[string]} credentialLibraryIDs + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + addCredentialLibraries( + credentialLibraryIDs, + options = { adapterOptions: {} } + ) { + const defaultAdapterOptions = { + method: 'add-credential-libraries', + credentialLibraryIDs, + }; + // There is no "deep merge" in ES. + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } + + /** + * Delete credential libraries via the `remove-credential-libraries` method. + * See serializer and adapter for more information. + * @param {[string]} credentialLibraryIDs + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + removeCredentialLibraries( + credentialLibraryIDs, + options = { adapterOptions: {} } + ) { + const defaultAdapterOptions = { + method: 'remove-credential-libraries', + credentialLibraryIDs, + }; + // There is no "deep merge" in ES. + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } + + /** + * Delete a single credential library set via the `remove-credential-libraries` method. + * @param {number} credentialLibraryID + * @param {object} options + * @return {Promise} + */ + removeCredentialLibrary(credentialLibraryID, options) { + return this.removeCredentialLibraries([credentialLibraryID], options); + } } diff --git a/addons/api/addon/serializers/fragment-credential-library.js b/addons/api/addon/serializers/fragment-credential-library.js deleted file mode 100644 index 9f3d01c81..000000000 --- a/addons/api/addon/serializers/fragment-credential-library.js +++ /dev/null @@ -1,16 +0,0 @@ -import JSONSerializer from '@ember-data/serializer/json'; - -export default class FragmentCredentialLibrarySerializer extends JSONSerializer { - // =methods - - /** - * Renames `id` to `credential_library_id` because fragments may not have IDs. - * Otherwise normalizes as normal. - * @param {ModelClass} typeClass - * @param {object} resourceHash - * @return {object} - */ - normalize(typeClass, { id: credential_library_id, ...obj }) { - return super.normalize(typeClass, { credential_library_id, ...obj }); - } -} diff --git a/addons/api/app/models/fragment-credential-library.js b/addons/api/app/models/fragment-credential-library.js deleted file mode 100644 index 4cee0ae38..000000000 --- a/addons/api/app/models/fragment-credential-library.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/models/fragment-credential-library'; diff --git a/addons/api/app/serializers/fragment-credential-library.js b/addons/api/app/serializers/fragment-credential-library.js deleted file mode 100644 index b60ecfef5..000000000 --- a/addons/api/app/serializers/fragment-credential-library.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/serializers/fragment-credential-library'; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 1fd364182..9e09af76e 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -366,10 +366,10 @@ export default function () { const target = targets.find(id); const updatedAttrs = { version: attrs.version, - hostSetIds: target.hostSetIds, }; // If adding host sets, push them into the array if (method === 'add-host-sets') { + updatedAttrs.hostSetIds = target.hostSetIds; attrs.hostSetIds.forEach((id) => { if (!updatedAttrs.hostSetIds.includes(id)) { updatedAttrs.hostSetIds.push(id); @@ -378,10 +378,28 @@ export default function () { } // If deleting host sets, filter them out of the array if (method === 'remove-host-sets') { + updatedAttrs.hostSetIds = target.hostSetIds; updatedAttrs.hostSetIds = updatedAttrs.hostSetIds.filter((id) => { return !attrs.hostSetIds.includes(id); }); } + // If adding credential libraries, push them into the array + if (method === 'add-credential-libraries') { + updatedAttrs.credentialLibraryIds = target.credentialLibraryIds; + attrs.applicationCredentialLibraryIds.forEach((id) => { + if (!updatedAttrs.credentialLibraryIds.includes(id)) { + updatedAttrs.credentialLibraryIds.push(id); + } + }); + } + // If deleting credential libraries, filter them out of the array + if (method === 'remove-credential-libraries') { + updatedAttrs.credentialLibraryIds = target.credentialLibraryIds; + updatedAttrs.credentialLibraryIds = + updatedAttrs.credentialLibraryIds.filter((id) => { + return !attrs.applicationCredentialLibraryIds.includes(id); + }); + } return target.update(updatedAttrs); } ); diff --git a/addons/api/mirage/factories/target.js b/addons/api/mirage/factories/target.js index 8ae03edbe..e40df6abf 100644 --- a/addons/api/mirage/factories/target.js +++ b/addons/api/mirage/factories/target.js @@ -40,7 +40,9 @@ export default factory.extend({ .models.filter(() => randomBoolean(hostSetChance)); randomlySelectedCredentialLibraries = server.schema.credentialLibraries - .where((credentialLibrary) => credentialLibrary.scopeId === target.scope.id) + .where( + (credentialLibrary) => credentialLibrary.scopeId === target.scope.id + ) .models.filter(() => randomBoolean()); target.update({ diff --git a/addons/api/mirage/serializers/target.js b/addons/api/mirage/serializers/target.js index c23330189..b970f4853 100644 --- a/addons/api/mirage/serializers/target.js +++ b/addons/api/mirage/serializers/target.js @@ -24,7 +24,7 @@ export default ApplicationSerializer.extend({ }); } if (model.credentialLibraryIds?.length) { - json.credential_libraries = model.credentialLibraryIds; + json.application_credential_library_ids = model.credentialLibraryIds; } return json; }, diff --git a/addons/api/tests/unit/models/fragment-credential-library-test.js b/addons/api/tests/unit/models/fragment-credential-library-test.js deleted file mode 100644 index f5014569e..000000000 --- a/addons/api/tests/unit/models/fragment-credential-library-test.js +++ /dev/null @@ -1,13 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Model | fragment credential library', function (hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function (assert) { - let store = this.owner.lookup('service:store'); - let model = store.createRecord('fragment-credential-library', {}); - assert.ok(model); - }); -}); diff --git a/addons/api/tests/unit/models/target-test.js b/addons/api/tests/unit/models/target-test.js index 18be834bd..1c6fa2b22 100644 --- a/addons/api/tests/unit/models/target-test.js +++ b/addons/api/tests/unit/models/target-test.js @@ -223,4 +223,162 @@ module('Unit | Model | target', function (hooks) { 'Target has two associated sessions loaded in the store' ); }); + + test('it has a `credentialLibraries` array of resolved model instances (if those instances are already in the store)', function (assert) { + assert.expect(4); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + application_credential_library_ids: [{ value: '1' }, { value: '2' }], + }, + }, + }); + const target = store.peekRecord('target', '123abc'); + assert.equal( + target.application_credential_library_ids.length, + 2, + 'Target has two entires in application_credential_library_ids' + ); + assert.equal( + target.credentialLibraries.length, + 0, + 'Target has no resolved credentialLibraries because they are not loaded yet' + ); + store.push({ + data: { + id: '1', + type: 'credential-library', + attributes: {}, + }, + }); + store.push({ + data: { + id: '2', + type: 'credential-library', + attributes: {}, + }, + }); + // Since `credentialLibraries` is computed on `application_credential_library_ids`, + // not the store itself, it's necessary to do this assignment to kick-off the + // computed update. + /* eslint-disable no-self-assign */ + target.application_credential_library_ids = + target.application_credential_library_ids; + /* eslint-enable no-self-assign */ + assert.equal( + target.application_credential_library_ids.length, + 2, + 'Target has two entires in application_credential_library_ids' + ); + assert.equal( + target.credentialLibraries.length, + 2, + 'Target has two resolved credentialLibraries' + ); + }); + + test('it has an `addCredentialLibraries` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/targets/123abc:add-credential-libraries', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + application_credential_library_ids: ['123_abc', 'foobar'], + version: 1, + }); + return { id: '123abc' }; + } + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + name: 'Target', + description: 'Description', + application_credential_library_ids: [{ value: '1' }, { value: '2' }], + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('target', '123abc'); + await model.addCredentialLibraries(['123_abc', 'foobar']); + }); + + test('it has a `removeCredentialLibraries` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/targets/123abc:remove-credential-libraries', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + application_credential_library_ids: ['1', '2'], + version: 1, + }); + return { id: '123abc' }; + } + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + name: 'Target', + description: 'Description', + application_credential_library_ids: [{ value: '1' }, { value: '2' }], + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('target', '123abc'); + await model.removeCredentialLibraries(['1', '2']); + }); + + test('it has a `removeCredentialLibrary` method that deletes a single credential library using `removeCredentialLibraries` method', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/targets/123abc:remove-credential-libraries', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + application_credential_library_ids: ['2'], + version: 1, + }); + return { id: '123abc' }; + } + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'target', + attributes: { + name: 'Target', + description: 'Description', + application_credential_library_ids: [{ value: '1' }, { value: '2' }], + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('target', '123abc'); + await model.removeCredentialLibrary('2'); + }); }); diff --git a/addons/api/tests/unit/serializers/application-test.js b/addons/api/tests/unit/serializers/application-test.js index a7040e876..c2fe164c9 100644 --- a/addons/api/tests/unit/serializers/application-test.js +++ b/addons/api/tests/unit/serializers/application-test.js @@ -203,6 +203,7 @@ module('Unit | Serializer | application', function (hooks) { name: 'Target 1', scope: { scope_id: 'o_123' }, host_sets: [], + application_credential_library_ids: [], }, relationships: {}, }, diff --git a/addons/api/tests/unit/serializers/fragment-credential-library-test.js b/addons/api/tests/unit/serializers/fragment-credential-library-test.js deleted file mode 100644 index 719d97955..000000000 --- a/addons/api/tests/unit/serializers/fragment-credential-library-test.js +++ /dev/null @@ -1,23 +0,0 @@ -import { module, test } from 'qunit'; -import { setupTest } from 'ember-qunit'; - -module('Unit | Serializer | fragment credential library', function (hooks) { - setupTest(hooks); - - // Replace this with your real tests. - test('it exists', function (assert) { - let store = this.owner.lookup('service:store'); - let serializer = store.serializerFor('fragment-credential-library'); - - assert.ok(serializer); - }); - - test('it serializes records', function (assert) { - let store = this.owner.lookup('service:store'); - let record = store.createRecord('fragment-credential-library', {}); - - let serializedRecord = record.serialize(); - - assert.ok(serializedRecord); - }); -}); diff --git a/addons/api/tests/unit/serializers/target-test.js b/addons/api/tests/unit/serializers/target-test.js index 255faa9ac..12dcacd4b 100644 --- a/addons/api/tests/unit/serializers/target-test.js +++ b/addons/api/tests/unit/serializers/target-test.js @@ -4,7 +4,7 @@ import { setupTest } from 'ember-qunit'; module('Unit | Serializer | target', function (hooks) { setupTest(hooks); - test('it serializes targets normally, without host sets', function (assert) { + test('it serializes targets normally, without host sets or credential libraries', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('target'); @@ -15,6 +15,7 @@ module('Unit | Serializer | target', function (hooks) { { host_set_id: '1', host_catalog_id: '2' }, { host_set_id: '3', host_catalog_id: '4' }, ], + application_credential_library_ids: [{ value: '1' }, { value: '2' }], scope: { scope_id: 'org_1', type: 'org', @@ -63,6 +64,27 @@ module('Unit | Serializer | target', function (hooks) { }); }); + test('it serializes only credential libraries and version when an `adapterOptions.credentialLibraryIDs` array is passed', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('target'); + const record = store.createRecord('target', { + name: 'User', + description: 'Description', + application_credential_library_ids: [{ value: '1' }, { value: '2' }], + version: 1, + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + credentialLibraryIDs: ['4', '5'], + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + application_credential_library_ids: ['4', '5'], + version: 1, + }); + }); + test('it normalizes records with array fields', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); @@ -75,6 +97,7 @@ module('Unit | Serializer | target', function (hooks) { { host_set_id: '1', host_catalog_id: '2' }, { host_set_id: '3', host_catalog_id: '4' }, ], + application_credential_library_ids: ['1'], }; const normalized = serializer.normalizeSingleResponse( store, @@ -92,13 +115,14 @@ module('Unit | Serializer | target', function (hooks) { { host_set_id: '1', host_catalog_id: '2' }, { host_set_id: '3', host_catalog_id: '4' }, ], + application_credential_library_ids: [{ value: '1' }], }, relationships: {}, }, }); }); - test('it normalizes missing host_sets to empty array', function (assert) { + test('it normalizes missing host_sets and credential libraries to empty array', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('target'); @@ -122,6 +146,7 @@ module('Unit | Serializer | target', function (hooks) { name: 'Target 1', scope: { scope_id: 'o_123' }, host_sets: [], + application_credential_library_ids: [], }, relationships: {}, }, diff --git a/ui/admin/tests/acceptance/sessions-test.js b/ui/admin/tests/acceptance/sessions-test.js index 15a263925..d5f68ffeb 100644 --- a/ui/admin/tests/acceptance/sessions-test.js +++ b/ui/admin/tests/acceptance/sessions-test.js @@ -55,7 +55,7 @@ module('Acceptance | sessions', function (hooks) { 'target', 1, { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); instances.sessions = this.server.createList( 'session', diff --git a/ui/desktop/tests/acceptance/origin-test.js b/ui/desktop/tests/acceptance/origin-test.js index eddad56bb..6eb0c5d22 100644 --- a/ui/desktop/tests/acceptance/origin-test.js +++ b/ui/desktop/tests/acceptance/origin-test.js @@ -97,7 +97,7 @@ module('Acceptance | origin', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); urls.scopes.global = `/scopes/${instances.scopes.global.id}`; diff --git a/ui/desktop/tests/acceptance/projects-test.js b/ui/desktop/tests/acceptance/projects-test.js index 38d54b480..3f6dd5506 100644 --- a/ui/desktop/tests/acceptance/projects-test.js +++ b/ui/desktop/tests/acceptance/projects-test.js @@ -92,7 +92,7 @@ module('Acceptance | projects', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); urls.scopes.global = `/scopes/${instances.scopes.global.id}`; diff --git a/ui/desktop/tests/acceptance/projects/sessions-test.js b/ui/desktop/tests/acceptance/projects/sessions-test.js index 2cdbafc16..2b31e5eaa 100644 --- a/ui/desktop/tests/acceptance/projects/sessions-test.js +++ b/ui/desktop/tests/acceptance/projects/sessions-test.js @@ -97,7 +97,7 @@ module('Acceptance | projects | sessions', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); instances.session = this.server.create( 'session', diff --git a/ui/desktop/tests/acceptance/projects/targets-test.js b/ui/desktop/tests/acceptance/projects/targets-test.js index 81dfbb22b..2fb027eec 100644 --- a/ui/desktop/tests/acceptance/projects/targets-test.js +++ b/ui/desktop/tests/acceptance/projects/targets-test.js @@ -101,7 +101,7 @@ module('Acceptance | projects | targets', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); instances.session = this.server.create( diff --git a/ui/desktop/tests/acceptance/projects/targets/sessions-test.js b/ui/desktop/tests/acceptance/projects/targets/sessions-test.js index 6bfe8bd89..f687be5d1 100644 --- a/ui/desktop/tests/acceptance/projects/targets/sessions-test.js +++ b/ui/desktop/tests/acceptance/projects/targets/sessions-test.js @@ -106,7 +106,7 @@ module('Acceptance | projects | targets | sessions', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); instances.session = this.server.create( diff --git a/ui/desktop/tests/acceptance/scopes-test.js b/ui/desktop/tests/acceptance/scopes-test.js index 93ec928bb..71907a984 100644 --- a/ui/desktop/tests/acceptance/scopes-test.js +++ b/ui/desktop/tests/acceptance/scopes-test.js @@ -110,7 +110,7 @@ module('Acceptance | scopes', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); instances.session = this.server.create( diff --git a/ui/desktop/tests/acceptance/targets-test.js b/ui/desktop/tests/acceptance/targets-test.js index 85881b7ec..f92168c3a 100644 --- a/ui/desktop/tests/acceptance/targets-test.js +++ b/ui/desktop/tests/acceptance/targets-test.js @@ -94,7 +94,7 @@ module('Acceptance | targets', function (hooks) { instances.target = this.server.create( 'target', { scope: instances.scopes.project }, - 'withRandomHostSets' + 'withAssociations' ); urls.scopes.global = `/scopes/${instances.scopes.global.id}`; From af337626f56c6d004e7ed7a36cccd20ff569e885 Mon Sep 17 00:00:00 2001 From: dharini Date: Thu, 22 Jul 2021 09:37:41 -0700 Subject: [PATCH 07/13] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20fieldset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../form/credential-library/index.hbs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index d45f78b58..0e9e69e7c 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -74,6 +74,23 @@ {{/each}} + + <:title>{{t 'form.http_method.label'}} + <:description>{{t 'form.http_method.help'}} + <:body> + + {{#each this.httpMethods as |httpMethod|}} + + {{/each}} + + + Date: Thu, 22 Jul 2021 09:45:45 -0700 Subject: [PATCH 08/13] =?UTF-8?q?fix:=20=F0=9F=90=9B=20space?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/admin/app/components/form/credential-library/index.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index 0e9e69e7c..6a5395f4e 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -91,6 +91,7 @@ + Date: Thu, 22 Jul 2021 10:24:56 -0700 Subject: [PATCH 09/13] =?UTF-8?q?fix:=20=F0=9F=90=9B=20use=20named=20block?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/admin/app/components/form/credential-library/index.hbs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index 6a5395f4e..a3c7acbae 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -74,7 +74,7 @@ {{/each}} - + <:title>{{t 'form.http_method.label'}} <:description>{{t 'form.http_method.help'}} <:body> @@ -90,7 +90,7 @@ {{/each}} - + Date: Thu, 22 Jul 2021 16:26:05 -0400 Subject: [PATCH 10/13] fix: Surface fieldset as a form component --- addons/rose/addon/components/rose/form/index.hbs | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/rose/addon/components/rose/form/index.hbs b/addons/rose/addon/components/rose/form/index.hbs index 8c545ec22..e23e52c0b 100644 --- a/addons/rose/addon/components/rose/form/index.hbs +++ b/addons/rose/addon/components/rose/form/index.hbs @@ -20,6 +20,7 @@ input=(component 'rose/form/input' disabled=this.disabled) select=(component 'rose/form/select' disabled=this.disabled) textarea=(component 'rose/form/textarea' disabled=this.disabled) + fieldset=(component 'rose/form/fieldset') actions=(component (if this.showEditToggleButton 'rose/form/actions/edit-toggle' From 03ce6be3ee09c0b6996f41b2d3c2775ec92ea395 Mon Sep 17 00:00:00 2001 From: dharini Date: Thu, 22 Jul 2021 13:44:02 -0700 Subject: [PATCH 11/13] =?UTF-8?q?fix:=20=F0=9F=90=9B=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/components/form/credential-library/index.hbs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index a3c7acbae..322c35965 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -63,17 +63,6 @@ {{/if}} - - {{#each this.httpMethods as |httpMethod|}} - - {{/each}} - - <:title>{{t 'form.http_method.label'}} <:description>{{t 'form.http_method.help'}} From a6be658aa58c19672e5cfa5cef98094af9c634a0 Mon Sep 17 00:00:00 2001 From: Susmitha Girumala Date: Thu, 22 Jul 2021 17:42:37 -0400 Subject: [PATCH 12/13] fix: Add error handling to credential-library creation form --- addons/api/addon/models/credential-library.js | 8 +-- .../fragment-credential-library-attributes.js | 2 + addons/api/mirage/config.js | 1 - addons/core/translations/form/en-us.yaml | 9 --- addons/core/translations/resources/en-us.yaml | 15 +++- .../form/credential-library/index.hbs | 72 ++++++++++++------- .../form/credential-library/index.js | 10 ++- 7 files changed, 67 insertions(+), 50 deletions(-) diff --git a/addons/api/addon/models/credential-library.js b/addons/api/addon/models/credential-library.js index 4ca321a18..a77e605ba 100644 --- a/addons/api/addon/models/credential-library.js +++ b/addons/api/addon/models/credential-library.js @@ -6,7 +6,7 @@ import { fragment } from 'ember-data-model-fragments/attributes'; * Enum options for credential library. */ export const options = { - http_methods: ['GET', 'POST'], + http_method: ['GET', 'POST'], }; export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel { @@ -15,11 +15,7 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo // See the application adapter's error normalization method for // more information. - @attr('string', { - readOnly: true, - defaultValue: options.http_methods[0], - }) - attributes_http_method; + @attr('string', { readOnly: true }) attributes_http_method; @attr('string', { readOnly: true }) attributes_http_request_body; @attr('string', { readOnly: true }) attributes_path; diff --git a/addons/api/addon/models/fragment-credential-library-attributes.js b/addons/api/addon/models/fragment-credential-library-attributes.js index dd6138199..582922601 100644 --- a/addons/api/addon/models/fragment-credential-library-attributes.js +++ b/addons/api/addon/models/fragment-credential-library-attributes.js @@ -1,5 +1,6 @@ import Fragment from 'ember-data-model-fragments/fragment'; import { attr } from '@ember-data/model'; +import { options } from './credential-library'; export default class FragmentCredentialLibraryAttributesModel extends Fragment { //= attributes (vault) @@ -7,6 +8,7 @@ export default class FragmentCredentialLibraryAttributesModel extends Fragment { @attr('string', { description: 'The HTTP method the library uses when requesting credentials from Vault.', + defaultValue: () => options.http_method[0], }) http_method; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 5d9ddaf8e..a45cd1bab 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -484,7 +484,6 @@ export default function () { const attrs = this.normalizedRequestAttrs(); const credentialStore = credentialStores.find(attrs.credentialStoreId); attrs.scopeId = credentialStore.scope.id; - console.log(attrs); return credentialLibraries.create(attrs); } ); diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index 171011a4a..cb7b69832 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -115,12 +115,3 @@ value: callback_url: label: Callback URL help: The URL to which the third-party provider sends the authentication response. Paste this into the provider configuration. -vault_path: - label: Vault Path - help: The path in Vault to request credentials from. -http_method: - label: HTTP Method - help: The HTTP method the library uses when requesting credentials from Vault. -http_request_body: - label: HTTP Request Body - help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when http_method is set to POST. diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 9468e3500..d889b0975 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -280,4 +280,17 @@ credential-library: messages: none: title: No Credential Libraries Available - description: No Credential Libraries available in this credential store. \ No newline at end of file + description: No Credential Libraries available in this credential store. + form: + vault_path: + label: Vault Path + help: The path the library uses when requesting credentials from Vault. + http_method: + label: HTTP Method + help: The HTTP method the library uses when requesting credentials from Vault. + http_request_body: + label: HTTP Request Body + help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST. + http_post_method_options: + label: HTTP Method POST Options + help: The options available for when HTTP method is set to POST. \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index 322c35965..f55445503 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -48,15 +48,15 @@ @name='vault_path' @type='text' @value={{@model.attributes.path}} - @label={{t 'form.vault_path.label'}} - @error={{@model.errors.name}} - @helperText={{t 'form.vault_path.help'}} + @label={{t 'resources.credential-library.form.vault_path.label'}} + @error={{@model.errors.attributes_path}} + @helperText={{t 'resources.credential-library.form.vault_path.help'}} readonly={{false}} as |field| > - {{#if @model.errors.name}} + {{#if @model.errors.attributes_path}} - {{#each @model.errors.name as |error|}} + {{#each @model.errors.attributes_path as |error|}} {{error.message}} {{/each}} @@ -64,8 +64,10 @@ - <:title>{{t 'form.http_method.label'}} - <:description>{{t 'form.http_method.help'}} + <:title>{{t 'resources.credential-library.form.http_method.label'}} + <:description>{{t + 'resources.credential-library.form.http_method.help' + }} <:body> - {{#each this.httpMethods as |httpMethod|}} + {{#each this.httpMethodOptions as |httpMethod|}} {{/each}} - - {{#if @model.errors.description}} - - {{#each @model.errors.description as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - + {{#if this.isHttpRequestBodyAllowed}} + + <:title>{{t + 'resources.credential-library.form.http_post_method_options.label' + }} + <:description>{{t + 'resources.credential-library.form.http_post_method_options.help' + }} + <:body> + + {{#if @model.errors.attributes_http_request_body}} + + {{#each @model.errors.attributes_http_request_body as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{/if}} Date: Fri, 23 Jul 2021 09:29:42 -0400 Subject: [PATCH 13/13] fix: Shorten credential library POST form area --- addons/core/translations/resources/en-us.yaml | 5 +- .../form/credential-library/index.hbs | 54 ++++++++----------- 2 files changed, 22 insertions(+), 37 deletions(-) diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index b2ec4d304..8094abf9e 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -291,8 +291,5 @@ credential-library: label: HTTP Method help: The HTTP method the library uses when requesting credentials from Vault. http_request_body: - label: HTTP Request Body + label: HTTP Method POST Request Body help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST. - http_post_method_options: - label: HTTP Method POST Options - help: The options available for when HTTP method is set to POST. \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/index.hbs b/ui/admin/app/components/form/credential-library/index.hbs index f55445503..beef7e8c6 100644 --- a/ui/admin/app/components/form/credential-library/index.hbs +++ b/ui/admin/app/components/form/credential-library/index.hbs @@ -84,39 +84,27 @@ {{#if this.isHttpRequestBodyAllowed}} - - <:title>{{t - 'resources.credential-library.form.http_post_method_options.label' - }} - <:description>{{t - 'resources.credential-library.form.http_post_method_options.help' - }} - <:body> - - {{#if @model.errors.attributes_http_request_body}} - - {{#each @model.errors.attributes_http_request_body as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - - + + {{#if @model.errors.attributes_http_request_body}} + + {{#each @model.errors.attributes_http_request_body as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + {{/if}}