From 8952541203b963e8d72d5c41841e59b4b6cfb219 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Fri, 2 Sep 2022 12:29:39 -0700 Subject: [PATCH] Add injected credentials tab (#1295) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: 🤖 introduce injected application credentials on the UI --- addons/api/addon/models/credential.js | 9 +- .../index.hbs | 46 ++++++ .../index.js | 28 ++++ .../targets/target/actions/index.hbs | 10 ++ .../components/targets/target/nav/index.hbs | 11 ++ .../target/add-brokered-credential-sources.js | 14 +- ...injected-application-credential-sources.js | 49 ++++++ .../target/brokered-credential-sources.js | 2 +- ...injected-application-credential-sources.js | 20 +++ ...injected-application-credential-sources.js | 83 ++++++++++ .../target/brokered-credential-sources.js | 6 +- ...injected-application-credential-sources.js | 58 +++++++ ...njected-application-credential-sources.hbs | 54 +++++++ .../target/brokered-credential-sources.hbs | 2 +- ...njected-application-credential-sources.hbs | 142 ++++++++++++++++++ 15 files changed, 520 insertions(+), 14 deletions(-) create mode 100644 ui/admin/app/components/form/target/add-injected-application-credential-sources/index.hbs create mode 100644 ui/admin/app/components/form/target/add-injected-application-credential-sources/index.js create mode 100644 ui/admin/app/controllers/scopes/scope/targets/target/add-injected-application-credential-sources.js create mode 100644 ui/admin/app/controllers/scopes/scope/targets/target/injected-application-credential-sources.js create mode 100644 ui/admin/app/routes/scopes/scope/targets/target/add-injected-application-credential-sources.js create mode 100644 ui/admin/app/routes/scopes/scope/targets/target/injected-application-credential-sources.js create mode 100644 ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs create mode 100644 ui/admin/app/templates/scopes/scope/targets/target/injected-application-credential-sources.hbs diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index 2908d772b..877de41dc 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -1,3 +1,10 @@ import GeneratedCredentialModel from '../generated/models/credential'; +import { equal } from '@ember/object/computed'; -export default class CredentialModel extends GeneratedCredentialModel {} +export default class CredentialModel extends GeneratedCredentialModel { + // =attributes + /** + * @type {boolean} + */ + @equal('type', 'username_password') isUsernamePassword; +} diff --git a/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.hbs b/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.hbs new file mode 100644 index 000000000..955d0f3fe --- /dev/null +++ b/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.hbs @@ -0,0 +1,46 @@ + + + + + + + {{t 'form.id.label'}} + {{t 'form.name.label'}} + {{t 'form.type.label'}} + + + + {{#each @filteredCredentialSources as |credentialSource|}} + + + + + {{credentialSource.name}} + + {{t + (concat + 'resources.target.injected-application-credential-source.types.' + credentialSource.type + ) + }} + + + {{/each}} + + + diff --git a/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.js b/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.js new file mode 100644 index 000000000..c4c60f074 --- /dev/null +++ b/ui/admin/app/components/form/target/add-injected-application-credential-sources/index.js @@ -0,0 +1,28 @@ +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { A } from '@ember/array'; + +export default class FormTargetAddInjectedApplicationCredentialSourcesIndexComponent extends Component { + // =properties + + /** + * Array of selected credential source IDs. + * @type {EmberArray} + */ + selectedCredentialSourceIDs = A(); + + // =actions + + /** + * Add/Remove credential source to current selection + * @param {string} credentialSourceId + */ + @action + toggleCredentialSource(credentialSourceId) { + if (!this.selectedCredentialSourceIDs.includes(credentialSourceId)) { + this.selectedCredentialSourceIDs.addObject(credentialSourceId); + } else { + this.selectedCredentialSourceIDs.removeObject(credentialSourceId); + } + } +} diff --git a/ui/admin/app/components/targets/target/actions/index.hbs b/ui/admin/app/components/targets/target/actions/index.hbs index a421b15ed..200c4a56a 100644 --- a/ui/admin/app/components/targets/target/actions/index.hbs +++ b/ui/admin/app/components/targets/target/actions/index.hbs @@ -15,6 +15,16 @@ {{t 'resources.target.actions.add-brokered-credential-sources'}} {{/if}} + + {{#if (can 'addInjectedApplicationCredentialSources target' @model)}} + + {{t + 'resources.target.actions.add-injected-application-credential-sources' + }} + + {{/if}} {{#if (can 'delete model' @model)}} {{t 'resources.target.brokered-credential-source.title_plural'}} + {{#if @model.isSSH}} + + {{t + 'resources.target.injected-application-credential-source.title_plural' + }} + + {{/if}} + + diff --git a/ui/admin/app/controllers/scopes/scope/targets/target/add-brokered-credential-sources.js b/ui/admin/app/controllers/scopes/scope/targets/target/add-brokered-credential-sources.js index 067a3fce8..e568a09c7 100644 --- a/ui/admin/app/controllers/scopes/scope/targets/target/add-brokered-credential-sources.js +++ b/ui/admin/app/controllers/scopes/scope/targets/target/add-brokered-credential-sources.js @@ -1,6 +1,5 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -import { computed } from '@ember/object'; export default class ScopesScopeTargetsTargetAddBrokeredCredentialSourcesController extends Controller { // =services @@ -21,20 +20,19 @@ export default class ScopesScopeTargetsTargetAddBrokeredCredentialSourcesControl * Filter out credential sources not already added to the target. * @type {[CredentialLibraryModel, CredentialModel]} */ - @computed( - 'model.{target.brokered_credential_source_ids.[],credentialLibraries.[],credentials.[]}' - ) get filteredCredentialSources() { // Get IDs for credential sources already added to the current target - const currentCredentialSourceIDs = + const currentCredentialSourceIDs = new Set( this.model.target.brokered_credential_source_ids.map( (source) => source.value - ); + ) + ); + const notAddedCredentialLibraries = this.model.credentialLibraries.filter( - ({ id }) => !currentCredentialSourceIDs.includes(id) + ({ id }) => !currentCredentialSourceIDs.has(id) ); const notAddedCredentials = this.model.credentials.filter( - ({ id }) => !currentCredentialSourceIDs.includes(id) + ({ id }) => !currentCredentialSourceIDs.has(id) ); return [...notAddedCredentialLibraries, ...notAddedCredentials]; } diff --git a/ui/admin/app/controllers/scopes/scope/targets/target/add-injected-application-credential-sources.js b/ui/admin/app/controllers/scopes/scope/targets/target/add-injected-application-credential-sources.js new file mode 100644 index 000000000..3033b287b --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/targets/target/add-injected-application-credential-sources.js @@ -0,0 +1,49 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeTargetsTargetAddInjectedApplicationCredentialSourcesController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Checks for unassigned credential sources. + * @type {boolean} + */ + get hasAvailableInjectedApplicationCredentialSources() { + return this.filteredCredentialSources.length > 0; + } + + /** + * Filter out credential sources not already added to the target. + * @type {[CredentialLibraryModel, CredentialModel]} + */ + get filteredCredentialSources() { + // Get IDs for credential sources already added to the current target + const currentCredentialSourceIDs = new Set( + this.model.target.injected_application_credential_source_ids.map( + (source) => source.value + ) + ); + + const notAddedCredentialLibraries = this.model.credentialLibraries.filter( + ({ id }) => !currentCredentialSourceIDs.has(id) + ); + const notAddedCredentials = this.model.credentials.filter( + ({ id }) => !currentCredentialSourceIDs.has(id) + ); + return [...notAddedCredentialLibraries, ...notAddedCredentials]; + } + + /** + * Translated roles breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t( + 'resources.target.actions.add-injected-application-credential-sources' + ); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/targets/target/brokered-credential-sources.js b/ui/admin/app/controllers/scopes/scope/targets/target/brokered-credential-sources.js index 6752a1db3..8df38e8ad 100644 --- a/ui/admin/app/controllers/scopes/scope/targets/target/brokered-credential-sources.js +++ b/ui/admin/app/controllers/scopes/scope/targets/target/brokered-credential-sources.js @@ -1,7 +1,7 @@ import Controller from '@ember/controller'; import { inject as service } from '@ember/service'; -export default class ScopesScopeTargetsTargetCredentialSourcesController extends Controller { +export default class ScopesScopeTargetsTargetBrokeredCredentialSourcesController extends Controller { // =services @service intl; diff --git a/ui/admin/app/controllers/scopes/scope/targets/target/injected-application-credential-sources.js b/ui/admin/app/controllers/scopes/scope/targets/target/injected-application-credential-sources.js new file mode 100644 index 000000000..bacecb4dd --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/targets/target/injected-application-credential-sources.js @@ -0,0 +1,20 @@ +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; + +export default class ScopesScopeTargetsTargetInjectedApplicationCredentialSourcesController extends Controller { + // =services + + @service intl; + + // =attributes + + /** + * Translated credential library breadcrumb + * @type {string} + */ + get breadCrumb() { + return this.intl.t( + 'resources.target.injected-application-credential-source.title_plural' + ); + } +} diff --git a/ui/admin/app/routes/scopes/scope/targets/target/add-injected-application-credential-sources.js b/ui/admin/app/routes/scopes/scope/targets/target/add-injected-application-credential-sources.js new file mode 100644 index 000000000..cac142824 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/targets/target/add-injected-application-credential-sources.js @@ -0,0 +1,83 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { all } from 'rsvp'; +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; + +export default class ScopesScopeTargetsTargetAddInjectedApplicationCredentialSourcesRoute extends Route { + // =services + + @service intl; + @service router; + + // =methods + + /** + * Empty out any previously loaded credential sources. + */ + beforeModel() { + this.store.unloadAll('credential-library'); + this.store.unloadAll('credential'); + } + + /** + * Returns the current target and credential sources. + * @return {{target: TargetModel, credentialLibraries: [CredentialLibraryModel], credentials: [CredentialModel]}} + */ + async model() { + const target = this.modelFor('scopes.scope.targets.target'); + const { id: scope_id } = this.modelFor('scopes.scope'); + const credentialStores = await this.store.query('credential-store', { + scope_id, + }); + await all( + credentialStores.map(({ id: credential_store_id, isStatic }) => { + if (isStatic) { + return this.store.query('credential', { + credential_store_id, + }); + } else { + return this.store.query('credential-library', { + credential_store_id, + }); + } + }) + ); + const credentialLibraries = this.store.peekAll('credential-library'); + const credentials = this.store.peekAll('credential'); + return { + target, + credentialLibraries, + credentials, + }; + } + + // =actions + + /** + * Add credential libraries to current target + * @param {TargetModel} target + * @param {[string]} credentialLibraryIDs + */ + @action + @loading + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.add-success') + async save(target, credentialLibraryIDs) { + await target.addInjectedApplicationCredentialSources(credentialLibraryIDs); + this.router.replaceWith( + 'scopes.scope.targets.target.injected-application-credential-sources' + ); + } + + /** + * Redirect to target credential sources as if nothing ever happened. + */ + @action + cancel() { + this.router.replaceWith( + 'scopes.scope.targets.target.injected-application-credential-sources' + ); + } +} diff --git a/ui/admin/app/routes/scopes/scope/targets/target/brokered-credential-sources.js b/ui/admin/app/routes/scopes/scope/targets/target/brokered-credential-sources.js index 06fa838cf..4d092cb37 100644 --- a/ui/admin/app/routes/scopes/scope/targets/target/brokered-credential-sources.js +++ b/ui/admin/app/routes/scopes/scope/targets/target/brokered-credential-sources.js @@ -9,7 +9,7 @@ export default class ScopesScopeTargetsTargetBrokeredCredentialSourcesRoute exte // =methods /** - * Loads all credential libraries under the current target. + * Loads all credential sources under the current target. * @return {Promise{[CredentialLibraryModel, CredentialModel]}} */ beforeModel() { @@ -43,9 +43,9 @@ export default class ScopesScopeTargetsTargetBrokeredCredentialSourcesRoute exte // =actions /** - * Remove a credential library from the current target. + * Remove a credential source from the current target. * @param {TargetModel} target - * @param {CredentialLibraryModel} credentialLibrary + * @param {CredentialLibraryModel, credentialModel} credentialSource */ @action @loading diff --git a/ui/admin/app/routes/scopes/scope/targets/target/injected-application-credential-sources.js b/ui/admin/app/routes/scopes/scope/targets/target/injected-application-credential-sources.js new file mode 100644 index 000000000..f243169ee --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/targets/target/injected-application-credential-sources.js @@ -0,0 +1,58 @@ +import Route from '@ember/routing/route'; +import { all } from 'rsvp'; +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { confirm } from 'core/decorators/confirm'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; + +export default class ScopesScopeTargetsTargetInjectedApplicationCredentialSourcesRoute extends Route { + // =methods + + /** + * Loads all credential sources under the current target. + * @return {Promise{[CredentialLibraryModel, CredentialModel]}} + */ + beforeModel() { + const { injected_application_credential_source_ids: sourceIDFragments } = + this.modelFor('scopes.scope.targets.target'); + return all( + sourceIDFragments.map(({ value }) => { + const isStatic = value.includes('cred'); + if (isStatic) { + return this.store.findRecord('credential', value, { + reload: true, + }); + } else { + return this.store.findRecord('credential-library', value, { + reload: true, + }); + } + }) + ); + } + + /** + * Returns the previously loaded target instance. + * @return {TargetModel} + */ + model() { + return this.modelFor('scopes.scope.targets.target'); + } + + // =actions + + /** + * Remove a credential source from the current target. + * @param {TargetModel} target + * @param {CredentialLibraryModel, credentialModel} credentialSource + */ + @action + @loading + @confirm('questions.remove-confirm') + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.remove-success') + async removeInjectedApplicationCredentialSource(target, credentialSource) { + await target.removeInjectedApplicationCredentialSource(credentialSource.id); + this.refresh(); + } +} diff --git a/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs new file mode 100644 index 000000000..954f640fe --- /dev/null +++ b/ui/admin/app/templates/scopes/scope/targets/target/add-injected-application-credential-sources.hbs @@ -0,0 +1,54 @@ +{{page-title + (t 'resources.target.actions.add-injected-application-credential-sources') +}} + + + + + + +

+ {{t + 'resources.target.injected-application-credential-source.messages.add.title' + }} +

+

{{t + 'resources.target.injected-application-credential-source.messages.add.description' + }}

+
+ + + {{#if this.hasAvailableInjectedApplicationCredentialSources}} + + {{/if}} + + {{#unless this.hasAvailableInjectedApplicationCredentialSources}} + + + + {{t + 'resources.target.injected-application-credential-source.messages.none.description' + }} + + + + {{t 'actions.back'}} + + + + {{/unless}} + + +
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 dd6dbdb64..5afb14991 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 (eq brokeredCredentialSource.type 'username_password')}} + {{#if brokeredCredentialSource.isUsernamePassword}} + + + + + + + + + + + + + + + + + + + {{#if @model.injectedApplicationCredentialSources}} + + + + + {{t 'form.name.label'}} + {{t 'form.type.label'}} + {{t 'form.id.label'}} + {{t 'titles.actions'}} + + + + {{#each + @model.injectedApplicationCredentialSources + as |injectedApplicationCredentialSource| + }} + + + {{#if injectedApplicationCredentialSource.isUsernamePassword}} + + {{injectedApplicationCredentialSource.displayName}} + + {{else}} + + {{injectedApplicationCredentialSource.displayName}} + + {{/if}} + +

{{injectedApplicationCredentialSource.description}}

+
+ + {{t + (concat + 'resources.target.injected-application-credential-source.types.' + injectedApplicationCredentialSource.type + ) + }} + + + + {{injectedApplicationCredentialSource.id}} + + + + {{#if (can 'removeCredentialSources target' @model)}} + + + {{t 'actions.remove'}} + + + {{/if}} + +
+ {{/each}} +
+
+ + {{else}} + + + + + {{t + 'resources.target.injected-application-credential-source.messages.welcome.description' + }} + + + + {{t + 'resources.target.actions.add-injected-application-credential-sources' + }} + + + + + {{/if}} +
+ +