diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index c0d78cb93..9b76dd3d2 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -970,6 +970,7 @@ credential-library: help: vault-generic: Authorize sessions using credentials brokered by Vault vault-ssh-certificate: Authorize sessions using the SSH Secrets Engine in Vault + vault-ldap: Authorize sessions using the LDAP Secrets Engine in Vault credential: title: Credential title_plural: Credentials diff --git a/ui/admin/app/components/form/credential-library/mapping-overrides/index.hbs b/ui/admin/app/components/form/credential-library/mapping-overrides/index.hbs new file mode 100644 index 000000000..894013d87 --- /dev/null +++ b/ui/admin/app/components/form/credential-library/mapping-overrides/index.hbs @@ -0,0 +1,51 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + <:fieldset as |F|> + + {{t + 'resources.credential-library.form.credential_mapping_overrides.label' + }} + + + {{t + 'resources.credential-library.form.credential_mapping_overrides.help' + }} + + + {{#if @model.errors.credential_mapping_overrides}} + + {{#each @model.errors.credential_mapping_overrides as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + <:field as |F|> + + <:newKey as |N|> + + + <:key as |K|> + + + <:value as |V|> + + + + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/mapping-overrides/index.js b/ui/admin/app/components/form/credential-library/mapping-overrides/index.js new file mode 100644 index 000000000..9d50d724a --- /dev/null +++ b/ui/admin/app/components/form/credential-library/mapping-overrides/index.js @@ -0,0 +1,47 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { options } from 'api/models/credential-library'; +import { action } from '@ember/object'; + +export default class FormCredentialLibraryMappingOverridesComponent extends Component { + get mappingOverrides() { + return options.mapping_overrides[this.args.model.credential_type]; + } + + get allowedEntries() { + return this.mappingOverrides.length; + } + + /** + * Prevents users from selecting duplicate keys from the select list if the arg is set to true + * @type {array} + */ + get selectOptions() { + const previouslySelectedKeys = + this.args.model.credential_mapping_overrides || []; + if (previouslySelectedKeys.length) { + return this.mappingOverrides.filter((key) => + previouslySelectedKeys.every((obj) => obj.key !== key), + ); + } else { + return this.mappingOverrides; + } + } + + /** + * Determines if we need to show an empty row to the users to enter more key/value pairs based on removeDuplicates arg, + * by default it is true + * @type {object} + */ + @action + showNewRow() { + return ( + this.args.model.credential_mapping_overrides?.length !== + this.allowedEntries + ); + } +} diff --git a/ui/admin/app/components/form/credential-library/radio/index.hbs b/ui/admin/app/components/form/credential-library/radio/index.hbs new file mode 100644 index 000000000..e2bc8fd96 --- /dev/null +++ b/ui/admin/app/components/form/credential-library/radio/index.hbs @@ -0,0 +1,28 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + {{t 'form.type.label'}} + {{#each this.credentialTypes as |type|}} + + {{t + (concat 'resources.credential-library.types.' type) + }} + {{t + (concat 'resources.credential-library.help.' type) + }} + + {{/each}} + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/radio/index.js b/ui/admin/app/components/form/credential-library/radio/index.js new file mode 100644 index 000000000..4d6d2d3ba --- /dev/null +++ b/ui/admin/app/components/form/credential-library/radio/index.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Component from '@glimmer/component'; +import { + TYPES_CREDENTIAL_LIBRARY, + TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, +} from 'api/models/credential-library'; +import { service } from '@ember/service'; + +export default class FormCredentialLibraryRadioComponent extends Component { + // =services + @service features; + + /** + * Returns credential types, filtering out SSH if the feature is disabled. + * @returns {Array.} + */ + get credentialTypes() { + let types = TYPES_CREDENTIAL_LIBRARY; + return this.features.isEnabled('ssh-target') + ? types + : types.filter( + (type) => type !== TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + ); + } +} diff --git a/ui/admin/app/components/form/credential-library/vault-generic/index.hbs b/ui/admin/app/components/form/credential-library/vault-generic/index.hbs index 3473a1b65..f4b61eb27 100644 --- a/ui/admin/app/components/form/credential-library/vault-generic/index.hbs +++ b/ui/admin/app/components/form/credential-library/vault-generic/index.hbs @@ -51,30 +51,11 @@ {{/if}} - {{#if (and @model.isNew (feature-flag 'ssh-target'))}} - - {{t 'form.type.label'}} - {{#each this.types as |type|}} - - {{t - (concat 'resources.credential-library.types.' type) - }} - {{t - (concat 'resources.credential-library.help.' type) - }} - - {{/each}} - + {{#if @model.isNew}} + {{else}} - <:fieldset as |F|> - - {{t - 'resources.credential-library.form.credential_mapping_overrides.label' - }} - - - {{t - 'resources.credential-library.form.credential_mapping_overrides.help' - }} - - - {{#if @model.errors.credential_mapping_overrides}} - - {{#each @model.errors.credential_mapping_overrides as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - - <:field as |F|> - - <:newKey as |N|> - - - <:key as |K|> - - - <:value as |V|> - - - - - + /> {{/if}} } - */ - types = TYPES_CREDENTIAL_LIBRARY; - /** * Only allow HTTP request body field if http_method is set to POST. * @type {boolean} @@ -32,14 +23,6 @@ export default class FormCredentialLibraryVaultGenericComponent extends Componen return this.args.model.http_method?.match(/post/i); } - get mappingOverrides() { - return options.mapping_overrides[this.args.model.credential_type]; - } - - get allowedEntries() { - return this.mappingOverrides.length; - } - /** * Clear the previously selected key value pair when toggling between credential types on a new form */ @@ -48,34 +31,4 @@ export default class FormCredentialLibraryVaultGenericComponent extends Componen this.args.model.credential_mapping_overrides = []; this.args.model.credential_type = value; } - - /** - * Prevents users from selecting duplicate keys from the select list if the arg is set to true - * @type {array} - */ - - get selectOptions() { - const previouslySelectedKeys = - this.args.model.credential_mapping_overrides || []; - if (previouslySelectedKeys.length) { - return this.mappingOverrides.filter((key) => - previouslySelectedKeys.every((obj) => obj.key !== key), - ); - } else { - return this.mappingOverrides; - } - } - - /** - * Determines if we need to show an empty row to the users to enter more key/value pairs based on removeDuplicates arg, - * by default it is true - * @type {object} - */ - @action - showNewRow() { - return ( - this.args.model.credential_mapping_overrides?.length !== - this.allowedEntries - ); - } } diff --git a/ui/admin/app/components/form/credential-library/vault-ldap/index.hbs b/ui/admin/app/components/form/credential-library/vault-ldap/index.hbs new file mode 100644 index 000000000..56b8e9f10 --- /dev/null +++ b/ui/admin/app/components/form/credential-library/vault-ldap/index.hbs @@ -0,0 +1,126 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: BUSL-1.1 +}} + + + + {{t 'form.name.label'}} + {{t 'form.name.help'}} + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + + {{error.message}} + + {{/each}} + + {{/if}} + + + + {{t 'form.description.label'}} + {{t 'form.description.help'}} + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + {{else}} + + {{t 'form.type.label'}} + + {{t (concat 'resources.credential-library.help.' @model.type)}} + + + {{/if}} + + + {{t + 'resources.credential-library.form.vault_path.label' + }} + {{t + 'resources.credential-library.form.vault_path.help' + }} + {{#if @model.errors.path}} + + {{#each @model.errors.path as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t + 'resources.credential-library.form.credential_type.label' + }} + + {{t 'resources.credential-library.form.credential_type.description'}} + + + + + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs b/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs index 2008d19c0..11a8743fe 100644 --- a/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs +++ b/ui/admin/app/components/form/credential-library/vault-ssh-certificate/index.hbs @@ -54,29 +54,10 @@ {{#if @model.isNew}} - - {{t 'form.type.label'}} - {{#each this.types as |type|}} - - {{t - (concat 'resources.credential-library.types.' type) - }} - {{t - (concat 'resources.credential-library.help.' type) - }} - - {{/each}} - + {{else}} } - */ - types = TYPES_CREDENTIAL_LIBRARY; - /** * Boolean to determine if the key bits field should be displayed on the form * @returns {boolean} 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 index 07202c892..346d17ddd 100644 --- 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 @@ -5,7 +5,12 @@ import Route from '@ember/routing/route'; import { service } from '@ember/service'; -import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library'; +import { + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, +} from 'api/models/credential-library'; +import { TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN } from 'api/models/credential'; export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewRoute extends Route { // =services @@ -52,23 +57,34 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari 'scopes.scope.credential-stores.credential-store', ); - let name, description; + let name, description, credential_type; if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); } - // Set the type to generic vault if feature flag isn't enabled in cases where + // Set the type to generic vault if feature flag isn't enabled or type is not set in cases where // user sets the query parameter manually - type = this.features.isEnabled('ssh-target') - ? type - : TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC; + let resolvedType = type; + if ( + !this.features.isEnabled('ssh-target') && + resolvedType === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE + ) { + resolvedType = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC; + } + + // Set the credential type to username_password_domain if the type is vault ldap + if (resolvedType === TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP) { + credential_type = TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN; + } return this.store.createRecord('credential-library', { - type, + type: resolvedType, credential_store_id, name, description, + // credential_type is only set for vault ldap type + credential_type, }); } } diff --git a/ui/admin/tests/acceptance/credential-library/create-test.js b/ui/admin/tests/acceptance/credential-library/create-test.js index 352943338..671e3c53c 100644 --- a/ui/admin/tests/acceptance/credential-library/create-test.js +++ b/ui/admin/tests/acceptance/credential-library/create-test.js @@ -10,7 +10,10 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; -import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library'; +import { + TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, +} from 'api/models/credential-library'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; import { setRunOptions } from 'ember-a11y-testing/test-support'; @@ -427,4 +430,89 @@ module('Acceptance | credential-libraries | create', function (hooks) { ); assert.strictEqual(currentURL(), urls.credentialLibraries); }); + + test('can create a new credential library of type vault ldap', async function (assert) { + await visit(urls.newCredentialLibrary); + + await click(selectors.TYPE_VAULT_LDAP); + + await fillIn(selectors.FIELD_VAULT_PATH, selectors.FIELD_VAULT_PATH_VALUE); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + + await click(commonSelectors.SAVE_BTN); + + assert.strictEqual( + this.server.schema.credentialLibraries.where({ + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }).length, + 1, + ); + const credentialLibrary = this.server.schema.credentialLibraries.findBy({ + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }); + + assert.strictEqual( + credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.path, + selectors.FIELD_VAULT_PATH_VALUE, + ); + assert.strictEqual( + credentialLibrary.credentialType, + selectors.FIELD_CRED_TYPE_UPD_VALUE, + ); + }); + + test('can create a new credential library with username, password and domain type for vault ldap', async function (assert) { + const credentialLibraryCount = getCredentialLibraryCount(); + const usernamePasswordDomainCredentialLibraryCount = + getUsernamePasswordDomainCredentialLibraryCount(); + await visit(urls.newCredentialLibrary); + + await click(selectors.TYPE_VAULT_LDAP); + + await fillIn(selectors.FIELD_VAULT_PATH, selectors.FIELD_VAULT_PATH_VALUE); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + + await select( + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT, + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT_DOMAIN_VALUE, + ); + await fillIn(selectors.FIELD_CRED_MAP_OVERRIDES_INPUT, 'domain'); + + await click(selectors.FIELD_CRED_MAP_OVERRIDES_BTN); + await click(commonSelectors.SAVE_BTN); + + assert.strictEqual(getCredentialLibraryCount(), credentialLibraryCount + 1); + assert.strictEqual( + getUsernamePasswordDomainCredentialLibraryCount(), + usernamePasswordDomainCredentialLibraryCount + 1, + ); + + const credentialLibrary = this.server.schema.credentialLibraries.findBy({ + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }); + + assert.strictEqual( + credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.credentialType, + selectors.FIELD_CRED_TYPE_UPD_VALUE, + ); + assert.deepEqual(credentialLibrary.credentialMappingOverrides, { + domain_attribute: 'domain', + }); + }); + + test('default `vault-generic` credential library is selected when `ssh-target` feature is not enabled and user manually sets `type` in the query params', async function (assert) { + await visit(`${urls.newCredentialLibrary}?type=vault-ssh-certificate`); + + assert.dom(selectors.TYPE_VAULT_SSH_CERT).isNotVisible(); + assert.dom(selectors.TYPE_VAULT_LDAP).isNotChecked(); + assert.dom(selectors.TYPE_VAULT_GENERIC).isChecked(); + }); }); diff --git a/ui/admin/tests/acceptance/credential-library/delete-test.js b/ui/admin/tests/acceptance/credential-library/delete-test.js index 93e903ac7..7069074fe 100644 --- a/ui/admin/tests/acceptance/credential-library/delete-test.js +++ b/ui/admin/tests/acceptance/credential-library/delete-test.js @@ -15,7 +15,10 @@ import sinon from 'sinon'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; import { setRunOptions } from 'ember-a11y-testing/test-support'; -import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library'; +import { + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, +} from 'api/models/credential-library'; import { TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN } from 'api/models/credential'; module('Acceptance | credential-libraries | delete', function (hooks) { @@ -25,6 +28,7 @@ module('Acceptance | credential-libraries | delete', function (hooks) { let getCredentialLibraryCount; let getUsernamePasswordDomainCredentialLibraryCount; + let getVaultLDAPCredentialLibraryCount; const instances = { scopes: { @@ -42,6 +46,7 @@ module('Acceptance | credential-libraries | delete', function (hooks) { newCredentialLibrary: null, unknownCredentialLibrary: null, usernamePasswordDomainCredentialLibrary: null, + vaultLDAPCredentialLibrary: null, }; hooks.beforeEach(async function () { @@ -71,6 +76,14 @@ module('Acceptance | credential-libraries | delete', function (hooks) { credential_type: TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN, }, ); + instances.vaultLDAPCredentialLibrary = this.server.create( + 'credential-library', + { + scope: instances.scopes.project, + credentialStore: instances.credentialStore, + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }, + ); // Generate route URLs for resources urls.credentialStores = `/scopes/${instances.scopes.project.id}/credential-stores`; urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; @@ -79,6 +92,7 @@ module('Acceptance | credential-libraries | delete', function (hooks) { urls.newCredentialLibrary = `${urls.credentialLibraries}/new`; urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`; urls.usernamePasswordDomainCredentialLibrary = `${urls.credentialLibraries}/${instances.usernamePasswordDomainCredentialLibrary.id}`; + urls.vaultLDAPCredentialLibrary = `${urls.credentialLibraries}/${instances.vaultLDAPCredentialLibrary.id}`; // Generate resource counter getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; @@ -87,6 +101,11 @@ module('Acceptance | credential-libraries | delete', function (hooks) { credential_type: TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN, }).length; }; + getVaultLDAPCredentialLibraryCount = () => { + return this.server.schema.credentialLibraries.where({ + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }).length; + }; await authenticateSession({}); }); @@ -208,4 +227,18 @@ module('Acceptance | credential-libraries | delete', function (hooks) { usernamePasswordDomainCredentialLibraryCount - 1, ); }); + + test('can delete vault ldap credential library type', async function (assert) { + const vaultLDAPCredentialLibraryCount = + getVaultLDAPCredentialLibraryCount(); + await visit(urls.vaultLDAPCredentialLibrary); + + await click(selectors.MANAGE_DROPDOWN_CRED_LIB); + await click(selectors.MANAGE_DROPDOWN_CRED_LIB_DELETE); + + assert.strictEqual( + getVaultLDAPCredentialLibraryCount(), + vaultLDAPCredentialLibraryCount - 1, + ); + }); }); diff --git a/ui/admin/tests/acceptance/credential-library/list-test.js b/ui/admin/tests/acceptance/credential-library/list-test.js index 30d79275c..3f3940240 100644 --- a/ui/admin/tests/acceptance/credential-library/list-test.js +++ b/ui/admin/tests/acceptance/credential-library/list-test.js @@ -10,6 +10,7 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { setupSqlite } from 'api/test-support/helpers/sqlite'; import { authenticateSession } from 'ember-simple-auth/test-support'; import * as commonSelectors from 'admin/tests/helpers/selectors'; +import { TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP } from 'api/models/credential-library'; module('Acceptance | credential-libraries | list', function (hooks) { setupApplicationTest(hooks); @@ -48,10 +49,19 @@ module('Acceptance | credential-libraries | list', function (hooks) { scope: instances.scopes.project, credentialStore: instances.credentialStore, }); + instances.vaultLDAPCredentialLibrary = this.server.create( + 'credential-library', + { + scope: instances.scopes.project, + credentialStore: instances.credentialStore, + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }, + ); // Generate route URLs for resources urls.credentialStores = `/scopes/${instances.scopes.project.id}/credential-stores`; urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`; + urls.vaultLDAPCredentialLibrary = `${urls.credentialLibraries}/${instances.vaultLDAPCredentialLibrary.id}`; await authenticateSession({}); }); @@ -90,4 +100,12 @@ module('Acceptance | credential-libraries | list', function (hooks) { assert.dom(commonSelectors.HREF(urls.credentialLibraries)).isVisible(); }); + + test('User can navigate to vault ldap credential library details', async function (assert) { + await visit(urls.credentialLibraries); + + assert + .dom(commonSelectors.TABLE_RESOURCE_LINK(urls.vaultLDAPCredentialLibrary)) + .isVisible(); + }); }); diff --git a/ui/admin/tests/acceptance/credential-library/read-test.js b/ui/admin/tests/acceptance/credential-library/read-test.js index 7966a6c0e..54b77327f 100644 --- a/ui/admin/tests/acceptance/credential-library/read-test.js +++ b/ui/admin/tests/acceptance/credential-library/read-test.js @@ -12,6 +12,7 @@ import { authenticateSession } from 'ember-simple-auth/test-support'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, } from 'api/models/credential-library'; import * as commonSelectors from 'admin/tests/helpers/selectors'; import { setRunOptions } from 'ember-a11y-testing/test-support'; @@ -190,4 +191,24 @@ module('Acceptance | credential-libraries | read', function (hooks) { assert.strictEqual(currentURL(), url); }); + + test('visiting vault ldap credential library', async function (assert) { + const vaultLDAPCredentialLibrary = this.server.create( + 'credential-library', + { + scope: instances.scopes.project, + credentialStore: instances.credentialStore, + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }, + ); + + await visit(urls.credentialLibraries); + + const url = `${urls.credentialLibraries}/${vaultLDAPCredentialLibrary.id}`; + + await visit(url); + await a11yAudit(); + + assert.strictEqual(currentURL(), url); + }); }); diff --git a/ui/admin/tests/acceptance/credential-library/selectors.js b/ui/admin/tests/acceptance/credential-library/selectors.js index aa4de05b7..d66b0891d 100644 --- a/ui/admin/tests/acceptance/credential-library/selectors.js +++ b/ui/admin/tests/acceptance/credential-library/selectors.js @@ -37,6 +37,8 @@ export const FIELD_CRED_MAP_OVERRIDES_BTN = '[name="credential_mapping_overrides"] button'; export const TYPE_VAULT_SSH_CERT = '[value="vault-ssh-certificate"]'; +export const TYPE_VAULT_LDAP = '[value="vault-ldap"]'; +export const TYPE_VAULT_GENERIC = '[value="vault-generic"]'; export const FIELD_CRIT_OPTS_KEY = '[name="critical_options"] tbody td:nth-of-type(1) input'; diff --git a/ui/admin/tests/acceptance/credential-library/update-test.js b/ui/admin/tests/acceptance/credential-library/update-test.js index 1a38049fa..032712d16 100644 --- a/ui/admin/tests/acceptance/credential-library/update-test.js +++ b/ui/admin/tests/acceptance/credential-library/update-test.js @@ -13,6 +13,7 @@ import { Response } from 'miragejs'; import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, } from 'api/models/credential-library'; import * as selectors from './selectors'; import * as commonSelectors from 'admin/tests/helpers/selectors'; @@ -72,6 +73,14 @@ module('Acceptance | credential-libraries | update', function (hooks) { credential_type: TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN, }, ); + instances.vaultLDAPCredentialLibrary = this.server.create( + 'credential-library', + { + scope: instances.scopes.project, + credentialStore: instances.credentialStore, + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }, + ); // Generate route URLs for resources urls.globalScope = `/scopes/global/scopes`; urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; @@ -83,6 +92,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { urls.newCredentialLibrary = `${urls.credentialLibraries}/new`; urls.unknownCredentialLibrary = `${urls.credentialLibraries}/foo`; urls.usernamePasswordDomainCredentialLibrary = `${urls.credentialLibraries}/${instances.usernamePasswordDomainCredentialLibrary.id}`; + urls.vaultLDAPCredentialLibrary = `${urls.credentialLibraries}/${instances.vaultLDAPCredentialLibrary.id}`; await authenticateSession({}); }); @@ -108,6 +118,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { }); await visit(urls.credentialLibrary); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await click(commonSelectors.CANCEL_BTN); @@ -237,6 +248,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { ); }); await visit(urls.credentialLibrary); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await click(commonSelectors.SAVE_BTN); @@ -269,6 +281,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { ); await visit(urls.credentialLibrary); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); @@ -309,6 +322,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { ); await visit(urls.credentialLibrary); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); @@ -340,6 +354,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { }); await visit(urls.credentialLibrary); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); assert.dom(selectors.FIELD_CRED_TYPE).doesNotExist(); @@ -363,6 +378,7 @@ module('Acceptance | credential-libraries | update', function (hooks) { await visit( `${urls.credentialLibraries}/${instances.credentialLibrary.id}`, ); + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); await fillIn( @@ -425,4 +441,55 @@ module('Acceptance | credential-libraries | update', function (hooks) { ext_key: 'ext_value', }); }); + + test('cannot update credential type in a vault ldap credential library', async function (assert) { + await visit(urls.vaultLDAPCredentialLibrary); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + + assert.dom(selectors.FIELD_CRED_TYPE).doesNotExist(); + }); + + test('can update a vault ldap credential library and save changes', async function (assert) { + await visit(urls.vaultLDAPCredentialLibrary); + + await click(commonSelectors.EDIT_BTN, 'Activate edit mode'); + await fillIn(commonSelectors.FIELD_NAME, commonSelectors.FIELD_NAME_VALUE); + await fillIn( + commonSelectors.FIELD_DESCRIPTION, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + + await fillIn(selectors.FIELD_VAULT_PATH, selectors.FIELD_VAULT_PATH_VALUE); + + await select( + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT, + selectors.FIELD_CRED_MAP_OVERRIDES_SELECT_DOMAIN_VALUE, + ); + + await fillIn(selectors.FIELD_CRED_MAP_OVERRIDES_INPUT, 'domain'); + await click(selectors.FIELD_CRED_MAP_OVERRIDES_BTN); + + await click(commonSelectors.SAVE_BTN); + + const credentialLibrary = this.server.schema.credentialLibraries.findBy({ + type: TYPE_CREDENTIAL_LIBRARY_VAULT_LDAP, + }); + + assert.strictEqual( + credentialLibrary.name, + commonSelectors.FIELD_NAME_VALUE, + ); + assert.strictEqual( + credentialLibrary.description, + commonSelectors.FIELD_DESCRIPTION_VALUE, + ); + assert.strictEqual( + credentialLibrary.attributes.path, + selectors.FIELD_VAULT_PATH_VALUE, + ); + assert.deepEqual(credentialLibrary.credentialMappingOverrides, { + domain_attribute: 'domain', + }); + }); }); 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 07612fdda..9237720ce 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 @@ -18,8 +18,11 @@ import { TYPE_CREDENTIAL_USERNAME_PASSWORD_DOMAIN, TYPE_CREDENTIAL_JSON, } from 'api/models/credential'; -import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library'; import { setRunOptions } from 'ember-a11y-testing/test-support'; +import { + TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, +} from 'api/models/credential-library'; module( 'Acceptance | targets | injected application credential sources', @@ -96,14 +99,18 @@ module( scope: instances.scopes.project, credentialStore: instances.staticCredentialStore, }); - instances.credentialLibraries = this.server.createList( - 'credential-library', - 6, - { + instances.credentialLibraries = [ + ...this.server.createList('credential-library', 3, { scope: instances.scopes.project, credentialStore: instances.vaultCredentialStore, - }, - ); + type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC, + }), + ...this.server.createList('credential-library', 3, { + scope: instances.scopes.project, + credentialStore: instances.vaultCredentialStore, + type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE, + }), + ]; instances.credentialLibrary = instances.credentialLibraries[0]; instances.credential = instances.credentials[0];