From 3b284fac66b5222c6c49052c93fbc78ed73f17a0 Mon Sep 17 00:00:00 2001 From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com> Date: Thu, 27 Jul 2023 14:25:40 -0700 Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20update=20to=20allow?= =?UTF-8?q?=20state=20change=20&=20update=20edit=20action=20(#1797)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 💡 update to allow state change & update edit action ✅ Closes: ICU-10078 * test: 💍 add more update and delete test cases * refactor: 💡 auth-method serializer * refactor: 💡 add secret input fields * refactor: 💡 remove changes to ldap auth method serializer --------- Co-authored-by: Dharini Jeeva --- addons/api/addon/models/auth-method.js | 7 +- addons/core/translations/resources/en-us.yaml | 4 +- .../auth-method/actions/index.hbs | 4 +- .../form/auth-method/ldap/index.hbs | 60 ++++- .../components/form/auth-method/ldap/index.js | 13 + .../scopes/scope/auth-methods/auth-method.js | 10 +- .../acceptance/auth-methods/delete-test.js | 168 +++++++++++-- .../acceptance/auth-methods/update-test.js | 229 +++++++++++++++--- 8 files changed, 426 insertions(+), 69 deletions(-) diff --git a/addons/api/addon/models/auth-method.js b/addons/api/addon/models/auth-method.js index 083d6fb1f..db128cfc8 100644 --- a/addons/api/addon/models/auth-method.js +++ b/addons/api/addon/models/auth-method.js @@ -20,6 +20,7 @@ export const TYPES_AUTH_METHOD = Object.freeze([ * Enum options per auth method type and field. */ export const options = { + state: ['inactive', 'active-private', 'active-public'], oidc: { signing_algorithms: [ 'RS256', @@ -33,7 +34,6 @@ export const options = { 'PS512', 'EdDSA', ], - state: ['inactive', 'active-private', 'active-public'], account_claim_maps: { to: ['sub', 'name', 'email'], }, @@ -42,7 +42,6 @@ export const options = { account_attribute_maps: { to: ['fullName', 'email'], }, - state: ['inactive', 'active-private', 'active-public'], }, }; @@ -118,9 +117,11 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel { */ changeState(state, options = { adapterOptions: {} }) { const defaultAdapterOptions = { - method: 'change-state', state, }; + if (this.isOIDC) { + defaultAdapterOptions.method = 'change-state'; + } return this.save({ ...options, adapterOptions: { diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 5e50a8007..e783bbad9 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -88,7 +88,7 @@ auth-method: help: Used to provide to the LDAP server for mTLS connections. Must be x509 PEM encoded. client_certificate_key: label: Client TLS Key - help: Used with the ClientTLSCert for mTLS connections to the LDAP server. Must be x509 PEM encoded. + help: Used with the ClientTLSCert for mTLS connections to the LDAP server. Must be x509 PEM encoded. We will not show this data after it is saved, but you can replace it. start_tls: label: Use StartTLS help: Use StartTLS to create a secure connection. @@ -100,7 +100,7 @@ auth-method: help: Distinguished name of entry to bind when performing user and group search. bind_password: label: Bind Password - help: Password to use with BindDN when searching for user. + help: Password to use with BindDN when searching for user. We will not show this data after it is saved, but you can replace it. upn_domain: label: UPN Domain help: Add this domain to the username when authenticating. diff --git a/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs b/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs index 31dcc3b62..0ae555d25 100644 --- a/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs +++ b/ui/admin/app/components/auth-methods/auth-method/actions/index.hbs @@ -3,7 +3,7 @@ SPDX-License-Identifier: MPL-2.0 }} -{{#if @model.isOIDC}} +{{#if (or @model.isOIDC @model.isLDAP)}} - {{#each @model.options.oidc.state as |state|}} + {{#each @model.options.state as |state|}} {{t 'resources.auth-method.section.connection.title' }}

- {{t + {{t 'resources.auth-method.section.connection.help' }} @@ -210,6 +210,32 @@ {{/if}} + {{else}} + + {{t + 'resources.auth-method.form.client_certificate_key.label' + }} + {{t + 'resources.auth-method.form.client_certificate_key.help' + }} + {{#if @model.errors.client_certificate_key}} + + {{#each @model.errors.client_certificate_key as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + {{/if}} {{t 'resources.auth-method.section.authenticated-search.title' }}

- {{t + {{t 'resources.auth-method.section.authenticated-search.help' }} @@ -291,6 +317,30 @@ {{/if}} + {{else}} + + {{t 'resources.auth-method.form.bind_password.label'}} + {{t + 'resources.auth-method.form.bind_password.help' + }} + {{#if @model.errors.bind_password}} + + {{#each @model.errors.bind_password as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + {{/if}} {{t 'resources.auth-method.section.anonymous-search.title' }}

- {{t + {{t 'resources.auth-method.section.anonymous-search.help' }} @@ -357,7 +407,7 @@

{{t 'resources.auth-method.section.user-entries.title' }}

- {{t + {{t 'resources.auth-method.section.user-entries.help' }} @@ -552,7 +602,7 @@

{{t 'resources.auth-method.section.group-entries.title' }}

- {{t + {{t 'resources.auth-method.section.group-entries.help' }} diff --git a/ui/admin/app/components/form/auth-method/ldap/index.js b/ui/admin/app/components/form/auth-method/ldap/index.js index 201b7b038..ed2f50c51 100644 --- a/ui/admin/app/components/form/auth-method/ldap/index.js +++ b/ui/admin/app/components/form/auth-method/ldap/index.js @@ -7,6 +7,7 @@ import Component from '@glimmer/component'; import { action } from '@ember/object'; import { tracked } from '@glimmer/tracking'; import { options } from 'api/models/auth-method'; +import { set } from '@ember/object'; export default class FormAuthMethodLdapComponent extends Component { // =attributes @@ -83,4 +84,16 @@ export default class FormAuthMethodLdapComponent extends Component { parseUrlsArray() { return (this.args.model.urls || []).map((item) => item.value).toString(); } + + /** + * @param {string} currentAttr + */ + @action + rollbackSecretAttrs(currentAttr) { + const changedAttrs = this.args.model.changedAttributes(); + if (currentAttr in changedAttrs) { + const [oldVal] = changedAttrs[currentAttr]; + set(this.args.model, currentAttr, oldVal); + } + } } diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js index 6cadb9cdf..baedad4ba 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js @@ -59,10 +59,18 @@ export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route { authMethod.account_claim_maps ); } + if (authMethod.certificates) { + authMethod.certificates = structuredClone(authMethod.certificates); + } + if (authMethod.account_attribute_maps) { + authMethod.account_attribute_maps = structuredClone( + authMethod.account_attribute_maps + ); + } } /** - * Update state of OIDC auth method + * Update state of OIDC or LDAP auth method * @param {string} state */ @action diff --git a/ui/admin/tests/acceptance/auth-methods/delete-test.js b/ui/admin/tests/acceptance/auth-methods/delete-test.js index 782e4bfbc..84e2d3071 100644 --- a/ui/admin/tests/acceptance/auth-methods/delete-test.js +++ b/ui/admin/tests/acceptance/auth-methods/delete-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { visit, click, find } from '@ember/test-helpers'; +import { visit, click, currentURL } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -15,23 +15,34 @@ import { // //currentSession, // //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_AUTH_METHOD_LDAP } from 'api/models/auth-method'; module('Acceptance | auth methods | delete', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let featuresService; + let getAuthMethodCount; + + const DELETE_DROPDOWN_SELECTOR = + '.rose-layout-page-actions .rose-dropdown-button-danger'; + const DIALOG_DELETE_BTN_SELECTOR = '.rose-dialog .rose-button-primary'; + const DIALOG_CANCEL_BTN_SELECTOR = '.rose-dialog .rose-button-secondary'; + const ERROR_MSG_SELECTOR = '.rose-notification-body'; + const instances = { scopes: { global: null, org: null, }, authMethod: null, + ldapAuthMethod: null, }; const urls = { orgScope: null, authMethods: null, - newAuthMethod: null, authMethod: null, + ldapAuthMethod: null, }; hooks.beforeEach(function () { @@ -45,20 +56,42 @@ module('Acceptance | auth methods | delete', function (hooks) { instances.authMethod = this.server.create('auth-method', { scope: instances.scopes.org, }); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; - urls.newAuthMethod = `${urls.authMethods}/new?type=password`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; + featuresService = this.owner.lookup('service:features'); + getAuthMethodCount = () => + this.server.schema.authMethods.all().models.length; }); test('can delete an auth method', async function (assert) { assert.expect(1); - const authMethodsCount = this.server.db.authMethods.length; - await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); - assert.strictEqual(this.server.db.authMethods.length, authMethodsCount - 1); + const authMethodsCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + + assert.strictEqual(getAuthMethodCount(), authMethodsCount - 1); + }); + + test('can delete an ldap auth method', async function (assert) { + assert.expect(1); + featuresService.enable('ldap-auth-methods'); + const authMethodsCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + + assert.strictEqual(getAuthMethodCount(), authMethodsCount - 1); }); test('errors are displayed when delete on an auth method fails', async function (assert) { @@ -74,14 +107,36 @@ module('Acceptance | auth methods | delete', function (hooks) { } ); }); - await visit(urls.authMethod); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); await a11yAudit(); - assert.strictEqual( - find('.rose-notification-body').textContent.trim(), - 'Oops.', - 'Displays primary error message.' - ); + + assert.dom(ERROR_MSG_SELECTOR).hasText('Oops.'); + }); + + test('errors are displayed when delete on an ldap auth method fails', async function (assert) { + assert.expect(1); + featuresService.enable('ldap-auth-methods'); + this.server.del('/auth-methods/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await a11yAudit(); + + assert.dom(ERROR_MSG_SELECTOR).hasText('Oops.'); }); test('cannot delete an auth method without proper authorization', async function (assert) { @@ -90,9 +145,86 @@ module('Acceptance | auth methods | delete', function (hooks) { instances.authMethod.authorized_actions.filter( (item) => item !== 'delete' ); - await visit(urls.authMethod); - assert.notOk( - find('.rose-layout-page-actions .rose-dropdown-button-danger') - ); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.dom(DELETE_DROPDOWN_SELECTOR).doesNotExist(); + }); + + test('cannot delete an ldap auth method without proper authorization', async function (assert) { + assert.expect(1); + featuresService.enable('ldap-auth-methods'); + instances.ldapAuthMethod.authorized_actions = + instances.ldapAuthMethod.authorized_actions.filter( + (item) => item !== 'delete' + ); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.dom(DELETE_DROPDOWN_SELECTOR).doesNotExist(); + }); + + test('user can accept delete auth method via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const authMethodCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await click(DIALOG_DELETE_BTN_SELECTOR); + + assert.strictEqual(currentURL(), urls.authMethods); + assert.strictEqual(getAuthMethodCount(), authMethodCount - 1); + }); + + test('user can accept delete ldap auth method via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + featuresService.enable('ldap-auth-methods'); + const authMethodCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await click(DIALOG_DELETE_BTN_SELECTOR); + + assert.strictEqual(currentURL(), urls.authMethods); + assert.strictEqual(getAuthMethodCount(), authMethodCount - 1); + }); + + test('user can cancel delete auth method via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + const authMethodCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await click(DIALOG_CANCEL_BTN_SELECTOR); + + assert.strictEqual(currentURL(), urls.authMethod); + assert.strictEqual(getAuthMethodCount(), authMethodCount); + }); + + test('user can cancel delete ldap auth method via dialog', async function (assert) { + assert.expect(2); + const confirmService = this.owner.lookup('service:confirm'); + confirmService.enabled = true; + featuresService.enable('ldap-auth-methods'); + const authMethodCount = getAuthMethodCount(); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await click(DIALOG_CANCEL_BTN_SELECTOR); + + assert.strictEqual(currentURL(), urls.ldapAuthMethod); + assert.strictEqual(getAuthMethodCount(), authMethodCount); }); }); diff --git a/ui/admin/tests/acceptance/auth-methods/update-test.js b/ui/admin/tests/acceptance/auth-methods/update-test.js index 0c06f2ed6..0f1d7dc46 100644 --- a/ui/admin/tests/acceptance/auth-methods/update-test.js +++ b/ui/admin/tests/acceptance/auth-methods/update-test.js @@ -4,14 +4,7 @@ */ import { module, test } from 'qunit'; -import { - visit, - click, - find, - fillIn, - select, - findAll, -} from '@ember/test-helpers'; +import { visit, click, fillIn, select, findAll } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; @@ -22,24 +15,38 @@ import { // //currentSession, // //invalidateSession, } from 'ember-simple-auth/test-support'; -import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; +import { + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +} from 'api/models/auth-method'; module('Acceptance | auth methods | update', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let featuresService; + + const BUTTON_SELECTOR = '.rose-form-actions [type="button"]'; + const SAVE_BTN_SELECTOR = '.rose-form-actions [type="submit"]'; + const NAME_INPUT_SELECTOR = '[name="name"]'; + const DESC_INPUT_SELECTOR = '[name="description"]'; + const ERROR_MSG_SELECTOR = '.rose-notification-body'; + const FIELD_ERROR_TEXT_SELECTOR = '.hds-form-error__message'; + const instances = { scopes: { global: null, org: null, }, authMethod: null, + ldapAuthMethod: null, }; const urls = { orgScope: null, authMethods: null, newAuthMethod: null, authMethod: null, + ldapAuthMethod: null, }; hooks.beforeEach(function () { @@ -53,21 +60,33 @@ module('Acceptance | auth methods | update', function (hooks) { instances.authMethod = this.server.create('auth-method', { scope: instances.scopes.org, }); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; urls.newAuthMethod = `${urls.authMethods}/new?type=password`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; + featuresService = this.owner.lookup('service:features'); }); test('can update an auth method and save changes', async function (assert) { assert.expect(1); - await visit(urls.authMethod); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'update name'); - await click('form [type="submit"]:not(:disabled)'); - assert.strictEqual(this.server.db.authMethods[0].name, 'update name'); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn(NAME_INPUT_SELECTOR, 'update name'); + await click(SAVE_BTN_SELECTOR); + + assert.strictEqual( + this.server.schema.authMethods.first().name, + 'update name' + ); }); test('can update an oidc auth method and save changes', async function (assert) { @@ -76,11 +95,13 @@ module('Acceptance | auth methods | update', function (hooks) { scope: instances.scopes.org, type: TYPE_AUTH_METHOD_OIDC, }); - await visit(`${urls.authMethods}/${instances.authMethod.id}`); - await click('form [type="button"]:not(:disabled)', 'Activate edit mode'); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethods}/${instances.authMethod.id}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); const name = 'oidc name'; - await fillIn('[name="name"]', name); - await fillIn('[name="description"]', 'description'); + await fillIn(NAME_INPUT_SELECTOR, name); + await fillIn(DESC_INPUT_SELECTOR, 'description'); await fillIn('[name="issuer"]', 'issuer'); await fillIn('[name="client_id"]', 'client_id'); await fillIn('[name="client_secret"]', 'client_secret'); @@ -147,13 +168,101 @@ module('Acceptance | auth methods | update', function (hooks) { assert.strictEqual(authMethod.attributes.api_url_prefix, 'api_url_prefix'); }); + test('can update an ldap auth method and save changes', async function (assert) { + assert.expect(19); + featuresService.enable('ldap-auth-methods'); + await visit(urls.authMethods); + const name = 'ldap auth method'; + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn(NAME_INPUT_SELECTOR, name); + await fillIn(DESC_INPUT_SELECTOR, 'description'); + await fillIn('[name="urls"]', 'url1,url2'); + await click('[name="certificates"] .hds-button--color-critical'); + // Remove certificate + await fillIn('[name="certificates"] textarea', 'certificate'); + await click('[name="certificates"] button'); + await click('[name="start_tls"]'); + await click('[name="insecure_tls"]'); + await fillIn('[name="bind_dn"]', 'bind dn'); + await fillIn('[name="upn_domain"]', 'upn domain'); + await click('[name="discover_dn"]'); + await click('[name="anon_group_search"]'); + await fillIn('[name="user_dn"]', 'user dn'); + await fillIn('[name="user_attr"]', 'user attr'); + await fillIn('[name="user_filter"]', 'user filter'); + // Remove all attribute maps + await Promise.all( + findAll( + '[name="account_attribute_maps"] .hds-button--color-critical' + ).map((element) => click(element)) + ); + await fillIn('[name="account_attribute_maps"] input', 'attribute'); + await select('[name="account_attribute_maps"] select', 'email'); + await click('[name="account_attribute_maps"] button'); + await fillIn('[name="group_dn"]', 'group dn'); + await fillIn('[name="group_attr"]', 'group attr'); + await fillIn('[name="group_filter"]', 'group filter'); + await click('[name="enable_groups"]'); + await click('[name="use_token_groups"]'); + await click(SAVE_BTN_SELECTOR); + + const ldapAuthMethod = this.server.schema.authMethods.findBy({ name }); + assert.strictEqual(ldapAuthMethod.name, name); + assert.strictEqual(ldapAuthMethod.description, 'description'); + assert.deepEqual(ldapAuthMethod.attributes.urls, ['url1', 'url2']); + assert.deepEqual(ldapAuthMethod.attributes.certificates, ['certificate']); + assert.true(ldapAuthMethod.attributes.start_tls); + assert.true(ldapAuthMethod.attributes.insecure_tls); + assert.strictEqual(ldapAuthMethod.attributes.bind_dn, 'bind dn'); + assert.strictEqual(ldapAuthMethod.attributes.upn_domain, 'upn domain'); + assert.true(ldapAuthMethod.attributes.discover_dn); + assert.true(ldapAuthMethod.attributes.anon_group_search); + assert.strictEqual(ldapAuthMethod.attributes.user_dn, 'user dn'); + assert.strictEqual(ldapAuthMethod.attributes.user_attr, 'user attr'); + assert.strictEqual(ldapAuthMethod.attributes.user_filter, 'user filter'); + assert.deepEqual(ldapAuthMethod.attributes.account_attribute_maps, [ + 'attribute=email', + ]); + assert.strictEqual(ldapAuthMethod.attributes.group_dn, 'group dn'); + assert.strictEqual(ldapAuthMethod.attributes.group_attr, 'group attr'); + assert.strictEqual(ldapAuthMethod.attributes.group_filter, 'group filter'); + assert.false(ldapAuthMethod.attributes.enable_groups); + assert.true(ldapAuthMethod.attributes.use_token_groups); + }); + test('can update an auth method and cancel changes', async function (assert) { assert.expect(1); await visit(urls.authMethod); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'update name'); - await click('form button:not([type="submit"])'); - assert.notEqual(this.server.db.authMethods[0].name, 'update name'); + + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn(NAME_INPUT_SELECTOR, 'update name'); + await click(BUTTON_SELECTOR, 'Cancel'); + + assert.notEqual(this.server.schema.authMethods.first().name, 'update name'); + }); + + test('can update an ldap auth method and cancel changes', async function (assert) { + assert.expect(2); + featuresService.enable('ldap-auth-methods'); + await visit(urls.authMethods); + const name = instances.ldapAuthMethod.name; + const ldapAuthMethod = this.server.schema.authMethods.findBy({ name }); + const { certificates, account_attribute_maps } = ldapAuthMethod.attributes; + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn('[name="certificates"] textarea', 'cert123'); + await fillIn('[name="account_attribute_maps"] input', 'attribute'); + await select('[name="account_attribute_maps"] select', 'email'); + await click(BUTTON_SELECTOR, 'Cancel'); + + assert.deepEqual(ldapAuthMethod.attributes.certificates, certificates); + assert.deepEqual( + ldapAuthMethod.attributes.account_attribute_maps, + account_attribute_maps + ); }); test('cannot make changes to an existing auth method without proper authorization', async function (assert) { @@ -162,8 +271,25 @@ module('Acceptance | auth methods | update', function (hooks) { instances.authMethod.authorized_actions.filter( (item) => item !== 'update' ); - await visit(urls.authMethod); - assert.notOk(find('.rose-layout-page-actions .rose-button-secondary')); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.dom(BUTTON_SELECTOR).doesNotExist(); + }); + + test('cannot make changes to an existing ldap auth method without proper authorization', async function (assert) { + assert.expect(1); + featuresService.enable('ldap-auth-methods'); + instances.ldapAuthMethod.authorized_actions = + instances.ldapAuthMethod.authorized_actions.filter( + (item) => item !== 'update' + ); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.dom(BUTTON_SELECTOR).doesNotExist(); }); test('saving an existing auth method with invalid fields displays error messages', async function (assert) { @@ -187,20 +313,47 @@ module('Acceptance | auth methods | update', function (hooks) { } ); }); - await visit(urls.authMethod); - await click('form [type="button"]', 'Activate edit mode'); - await fillIn('[name="name"]', 'existing auth method'); - await click('form [type="submit"]'); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn(NAME_INPUT_SELECTOR, 'existing auth method'); + await click(SAVE_BTN_SELECTOR); await a11yAudit(); - assert.strictEqual( - find('.rose-notification-body').textContent.trim(), - 'The request was invalid.', - 'Displays primary error message.' - ); - assert.strictEqual( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.', - 'Displays field-level errors.' - ); + assert.dom(ERROR_MSG_SELECTOR).hasText('The request was invalid.'); + assert.dom('.rose-form-error-message').hasText('Name is required.'); + }); + + test('saving an existing ldap auth method with invalid fields displays error messages', async function (assert) { + assert.expect(2); + featuresService.enable('ldap-auth-methods'); + this.server.patch('/auth-methods/:id', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'The request was invalid.', + details: { + request_fields: [ + { + name: 'name', + description: 'Name is required.', + }, + ], + }, + } + ); + }); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + await click(BUTTON_SELECTOR, 'Activate edit mode'); + await fillIn(NAME_INPUT_SELECTOR, 'existing auth method'); + await click(SAVE_BTN_SELECTOR); + await a11yAudit(); + assert.dom(ERROR_MSG_SELECTOR).hasText('The request was invalid.'); + assert.dom(FIELD_ERROR_TEXT_SELECTOR).hasText('Name is required.'); }); });