diff --git a/addons/api/addon/models/managed-group.js b/addons/api/addon/models/managed-group.js index bf3538b12..105618a96 100644 --- a/addons/api/addon/models/managed-group.js +++ b/addons/api/addon/models/managed-group.js @@ -6,6 +6,7 @@ import GeneratedManagedGroupModel from '../generated/models/managed-group'; import { attr } from '@ember-data/model'; import { A } from '@ember/array'; +import { TYPE_AUTH_METHOD_OIDC, TYPE_AUTH_METHOD_LDAP } from './auth-method'; export default class ManagedGroupModel extends GeneratedManagedGroupModel { // =attributes @@ -20,4 +21,18 @@ export default class ManagedGroupModel extends GeneratedManagedGroupModel { emptyArrayIfMissing: true, }) member_ids; + + /** + * @type {boolean} + */ + get isOIDC() { + return this.type === TYPE_AUTH_METHOD_OIDC; + } + + /** + * @type {boolean} + */ + get isLDAP() { + return this.type === TYPE_AUTH_METHOD_LDAP; + } } diff --git a/addons/api/addon/serializers/managed-group.js b/addons/api/addon/serializers/managed-group.js index 123586748..eb3f4b45f 100644 --- a/addons/api/addon/serializers/managed-group.js +++ b/addons/api/addon/serializers/managed-group.js @@ -11,6 +11,10 @@ import { export default class ManagedGroupSerializer extends ApplicationSerializer { // =properties + /** + * @type {boolean} + */ + serializeScopeID = false; /** * Delegates to a type-specific serialization, or default. diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index e783bbad9..15f297371 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -794,6 +794,10 @@ managed-group: messages: title: Welcome to Members description: No members in this managed group. + form: + group_names: + label: Group Names + help: The list of groups that this Managed Group belongs to. worker: title: Worker title_pki: New PKI Worker 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 0ae555d25..382877ac1 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 @@ -71,7 +71,7 @@ {{/if}} {{#if (can 'create model' @model collection='managed-groups')}} - {{#if @model.isOIDC}} + {{#if (or @model.isOIDC @model.isLDAP)}} {{t 'resources.managed-group.title_plural'}} diff --git a/ui/admin/app/components/form/managed-group/index.hbs b/ui/admin/app/components/form/managed-group/index.hbs index 316c7fb75..64ade2e9a 100644 --- a/ui/admin/app/components/form/managed-group/index.hbs +++ b/ui/admin/app/components/form/managed-group/index.hbs @@ -3,81 +3,14 @@ SPDX-License-Identifier: MPL-2.0 }} - - - - - {{#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.errors.filter_string}} - - {{#each @model.errors.filter_string as |error|}} - {{error.message}} - {{/each}} - - {{/if}} - - - {{#if (can 'save model' @model)}} - - {{/if}} - \ No newline at end of file +{{#if @model.type}} + {{component + (concat 'form/managed-group/' @model.type) + model=@model + edit=@edit + submit=@submit + cancel=@cancel + removeItemByIndex=@removeItemByIndex + addStringItem=@addStringItem + }} +{{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/form/managed-group/ldap/index.hbs b/ui/admin/app/components/form/managed-group/ldap/index.hbs new file mode 100644 index 000000000..4d90fb5b3 --- /dev/null +++ b/ui/admin/app/components/form/managed-group/ldap/index.hbs @@ -0,0 +1,134 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} + + + + {{t 'form.type.label'}} + + + + {{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}} + + + + {{t 'resources.managed-group.form.group_names.label'}} + {{t + 'resources.managed-group.form.group_names.help' + }} + {{#let (unique-id) as |valueHeaderID|}} + + + <:head as |H|> + + {{t 'titles.value'}} + {{t 'titles.actions'}} + + + <:body as |B|> + {{#each @model.group_names as |group i|}} + + + + + + + + + {{/each}} + + + + + + + + + + + + {{/let}} + {{#if @model.errors.group_names}} + + {{#each @model.errors.group_names as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/managed-group/ldap/index.js b/ui/admin/app/components/form/managed-group/ldap/index.js new file mode 100644 index 000000000..763adf4ed --- /dev/null +++ b/ui/admin/app/components/form/managed-group/ldap/index.js @@ -0,0 +1,24 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import Component from '@glimmer/component'; +import { action } from '@ember/object'; +import { tracked } from '@glimmer/tracking'; + +export default class FormManagedGroupLdapComponent extends Component { + // =attributes + /** + * @type {string} + */ + @tracked newGroup = ''; + + @action + addGroupName() { + if (this.newGroup) { + this.args.addStringItem('group_names', this.newGroup); + } + this.newGroup = ''; + } +} diff --git a/ui/admin/app/components/form/managed-group/oidc/index.hbs b/ui/admin/app/components/form/managed-group/oidc/index.hbs new file mode 100644 index 000000000..316c7fb75 --- /dev/null +++ b/ui/admin/app/components/form/managed-group/oidc/index.hbs @@ -0,0 +1,83 @@ +{{! + Copyright (c) HashiCorp, Inc. + SPDX-License-Identifier: MPL-2.0 +}} + + + + + + {{#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.errors.filter_string}} + + {{#each @model.errors.filter_string as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if (can 'save model' @model)}} + + {{/if}} + \ No newline at end of file diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/managed-groups/managed-group.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/managed-groups/managed-group.js index c971b8e85..4cd6eb30a 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/managed-groups/managed-group.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/managed-groups/managed-group.js @@ -4,6 +4,7 @@ */ import Route from '@ember/routing/route'; +import { action } from '@ember/object'; import { inject as service } from '@ember/service'; export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRoute extends Route { @@ -24,4 +25,21 @@ export default class ScopesScopeAuthMethodsAuthMethodManagedGroupsManagedGroupRo reload: true, }); } + + /** + * Copies the contents of string array fields in order to force the instance + * into a dirty state. This ensures that `model.rollbackAttributes()` reverts + * to the original expected array. + * + * The deep copy implemented here is required to ensure that both the + * array itself and its members are all new. + * + * @param {managedGroupModel} managedGroup + */ + @action + edit(managedGroup) { + if (managedGroup.group_names) { + managedGroup.group_names = structuredClone(managedGroup.group_names); + } + } } diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs index f0ef92ffc..c749f2ea8 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/accounts/index.hbs @@ -84,7 +84,7 @@ {{#if @model.managedGroups}} - - - - {{t 'form.name.label'}} - {{t 'form.description.label'}} - {{t 'form.type.label'}} - {{t 'form.id.label'}} - - - - - {{#each @model.managedGroups as |managed-group|}} - - - {{#if (can 'read model' managed-group)}} + {{#if @model.managedGroups.firstObject.isOIDC}} + + <:body as |B|> + + + {{#if (can 'read model' B.data)}} - {{managed-group.displayName}} + {{B.data.displayName}} {{else}} - {{managed-group.displayName}} + {{B.data.displayName}} {{/if}} - - {{managed-group.description}} - - - - + + {{B.data.description}} + + - {{managed-group.id}} + {{B.data.id}} - - - {{/each}} - - + + + + + {{else}} + + <:body as |B|> + + + {{#if (can 'read model' B.data)}} + + {{B.data.displayName}} + + {{else}} + {{B.data.displayName}} + {{/if}} + + {{B.data.description}} + + + + {{B.data.id}} + + + + {{truncate-list 'actions.more' B.data.group_names 3}} + + + + + {{/if}} {{else}} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs index cd458e1f4..7ac635b23 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/managed-groups/managed-group/members.hbs @@ -31,53 +31,73 @@ {{#if @model.members}} - - - - {{t 'form.name.label'}} - {{t 'form.issuer.label'}} - {{t 'form.subject.label'}} - {{t 'form.email.label'}} - {{t 'form.full_name.label'}} - {{t 'form.type.label'}} - {{t 'form.id.label'}} - - - - {{#each @model.members as |member|}} - - - {{member.accountName}} -

{{member.description}}

-
- - {{member.issuer}} - - - {{member.subject}} - - - {{member.email}} - - - {{member.full_name}} - - - - - + {{#if @model.members.firstObject.isOIDC}} + + <:body as |B|> + + + {{B.data.accountName}} +

{{B.data.description}}

+
+ {{B.data.issuer}} + {{B.data.subject}} + {{B.data.email}} + {{B.data.full_name}} + + - {{member.id}} + {{B.data.id}} -
-
- {{/each}} -
-
+ + + + + {{else}} + + <:body as |B|> + + + {{B.data.displayName}} +

{{B.data.description}}

+
+ {{B.data.email}} + {{B.data.full_name}} + {{B.data.dn}} + {{truncate-list + 'actions.more' + B.data.member_of_groups + 3 + }} +
+ +
+ {{/if}} {{else}}

{{t 'titles.new'}} - {{! TODO: change the icon link }} - +

@@ -22,6 +21,8 @@ @model={{@model}} @submit={{route-action 'save' @model}} @cancel={{route-action 'cancel' @model}} + @removeItemByIndex={{route-action 'removeItemByIndex' @model}} + @addStringItem={{route-action 'addStringItem' @model}} />
diff --git a/ui/admin/tests/acceptance/managed-groups/create-test.js b/ui/admin/tests/acceptance/managed-groups/create-test.js index 2d4802bfc..4a591266e 100644 --- a/ui/admin/tests/acceptance/managed-groups/create-test.js +++ b/ui/admin/tests/acceptance/managed-groups/create-test.js @@ -4,32 +4,48 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL, click, find, fillIn } from '@ember/test-helpers'; +import { visit, currentURL, click, fillIn } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -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 | managed-groups | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let getManagedGroupCount; + let featuresService; + + const SAVE_BTN_SELECTOR = '.rose-form-actions [type="submit"]'; + const CANCEL_BTN_SELECTOR = '.rose-form-actions [type="button"]'; + 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, - managedGroup: null, + ldapAuthMethod: null, }; const urls = { orgScope: null, authMethods: null, + authMethod: null, + ldapAuthMethod: null, managedGroups: null, + ldapManagedGroups: null, newManagedGroup: null, - managedGroup: null, + newLdapManagedGroup: null, }; hooks.beforeEach(function () { @@ -43,93 +59,126 @@ module('Acceptance | managed-groups | create', function (hooks) { scope: instances.scopes.org, type: TYPE_AUTH_METHOD_OIDC, }); - instances.managedGroup = this.server.create('managed-group', { + instances.ldapAuthMethod = this.server.create('auth-method', { scope: instances.scopes.org, - authMethod: instances.authMethod, + type: TYPE_AUTH_METHOD_LDAP, }); + // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; urls.managedGroups = `${urls.authMethod}/managed-groups`; + urls.ldapManagedGroups = `${urls.ldapAuthMethod}/managed-groups`; urls.newManagedGroup = `${urls.managedGroups}/new`; - urls.managedGroup = `${urls.managedGroups}/${instances.authMethod.id}`; - }); + urls.newLdapManagedGroup = `${urls.ldapManagedGroups}/new`; - hooks.afterEach(async function () { - const notification = find('.rose-notification'); - if (notification) { - await click('.rose-notification-dismiss'); - } - }); - - test('visiting managed groups', async function (assert) { - assert.expect(1); - await visit(urls.managedGroups); - await a11yAudit(); - assert.strictEqual(currentURL(), urls.managedGroups); + getManagedGroupCount = () => + this.server.schema.managedGroups.all().models.length; + featuresService = this.owner.lookup('service:features'); }); test('can create a new managed group', async function (assert) { - assert.expect(1); - const managedGroupsCount = this.server.db.managedGroups.length; - await visit(urls.newManagedGroup); - await fillIn('[name="name"]', 'Managed group name'); - await fillIn('[name="description"]', 'description'); - await click('form [type="submit"]:not(:disabled)'); - assert.strictEqual( - this.server.db.managedGroups.length, - managedGroupsCount + 1 - ); - }); - - test('User can not create a new managed group without proper authorization', async function (assert) { - assert.expect(2); - instances.authMethod.authorized_collection_actions['managed-groups'] = []; + assert.expect(3); + const managedGroupsCount = getManagedGroupCount(); + const name = 'Managed group name'; await visit(urls.authMethod); - assert.notOk( - instances.authMethod.authorized_collection_actions[ - 'managed-groups' - ].includes('create') - ); - assert.notOk( - find(`.rose-layout-page-actions [href="${urls.newManagedGroup}"]`) - ); + + await click(`[href="${urls.newManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, name); + await fillIn(DESC_INPUT_SELECTOR, 'description'); + await click(SAVE_BTN_SELECTOR); + + const managedGroup = this.server.schema.managedGroups.findBy({ name }); + assert.strictEqual(managedGroup.name, name); + assert.strictEqual(managedGroup.description, 'description'); + assert.strictEqual(getManagedGroupCount(), managedGroupsCount + 1); }); - test('User can navigate to new managed group route with proper authorization', async function (assert) { - assert.expect(2); - await visit(urls.managedGroups); - assert.ok( - instances.authMethod.authorized_collection_actions[ - 'managed-groups' - ].includes('create') - ); - assert.ok(find(`[href="${urls.newManagedGroup}"]`)); + test('can create a new ldap managed group', async function (assert) { + assert.expect(4); + const managedGroupsCount = getManagedGroupCount(); + const name = 'Managed group name'; + await visit(urls.ldapAuthMethod); + + await click(`[href="${urls.newLdapManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, name); + await fillIn(DESC_INPUT_SELECTOR, 'description'); + await fillIn('[name="group_names"] input', 'group name'); + await click('[name="group_names"] button'); + await click(SAVE_BTN_SELECTOR); + + const managedGroup = this.server.schema.managedGroups.findBy({ name }); + assert.strictEqual(managedGroup.name, name); + assert.strictEqual(managedGroup.description, 'description'); + assert.deepEqual(managedGroup.attributes.group_names, ['group name']); + assert.strictEqual(getManagedGroupCount(), managedGroupsCount + 1); }); - test('User cannot navigate to new managed group route without proper authorization', async function (assert) { + test('User cannot create a new managed group without proper authorization', async function (assert) { assert.expect(2); - instances.authMethod.authorized_collection_actions['managed-groups'] = []; - await visit(urls.managedGroups); - assert.notOk( + instances.authMethod.authorized_collection_actions['managed-groups'] = + instances.authMethod.authorized_collection_actions[ + 'managed-groups' + ].filter((item) => item !== 'create'); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.false( instances.authMethod.authorized_collection_actions[ 'managed-groups' ].includes('create') ); - assert.notOk(find(`[href="${urls.newManagedGroup}"]`)); + assert.dom(`[href="${urls.newManagedGroup}"]`).doesNotExist(); + }); + + test('User cannot create a new ldap managed group without proper authorization', async function (assert) { + assert.expect(2); + featuresService.enable('ldap-auth-methods'); + instances.ldapAuthMethod.authorized_collection_actions['managed-groups'] = + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].filter((item) => item !== 'create'); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.false( + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('create') + ); + assert.dom(`[href="${urls.newLdapManagedGroup}"]`).doesNotExist(); }); test('User can cancel a new managed group creation', async function (assert) { assert.expect(2); - const managedGroupsCount = this.server.db.managedGroups.length; - await visit(urls.newManagedGroup); - await fillIn('[name="name"]', 'Managed group name'); - await click('form button:not([type="submit"])'); - assert.strictEqual(this.server.db.managedGroups.length, managedGroupsCount); + const managedGroupsCount = getManagedGroupCount(); + await visit(urls.authMethod); + + await click(`[href="${urls.newManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, 'Managed group name'); + await click(CANCEL_BTN_SELECTOR); + + assert.strictEqual(getManagedGroupCount(), managedGroupsCount); assert.strictEqual(currentURL(), urls.managedGroups); }); + test('User can cancel a new ldap managed group creation', async function (assert) { + assert.expect(2); + const managedGroupsCount = getManagedGroupCount(); + await visit(urls.ldapAuthMethod); + + await click(`[href="${urls.newLdapManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, 'Managed group name'); + await click(CANCEL_BTN_SELECTOR); + + assert.strictEqual(getManagedGroupCount(), managedGroupsCount); + assert.strictEqual(currentURL(), urls.ldapManagedGroups); + }); + test('When user saving a new managed group with invalid fields displays error message', async function (assert) { assert.expect(2); this.server.post('/managed-groups', () => { @@ -151,19 +200,46 @@ module('Acceptance | managed-groups | create', function (hooks) { } ); }); - await visit(urls.newManagedGroup); - await fillIn('[name="name"]', 'new managed group'); - await click('form [type="submit"]'); + await visit(urls.authMethod); + + await click(`[href="${urls.newManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, 'new managed group'); + 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('When user saving a new ldap managed group with invalid fields displays error message', async function (assert) { + assert.expect(2); + this.server.post('/managed-groups', () => { + 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.ldapAuthMethod); + + await click(`[href="${urls.newLdapManagedGroup}"]`); + await fillIn(NAME_INPUT_SELECTOR, 'new managed group'); + 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.'); }); }); diff --git a/ui/admin/tests/acceptance/managed-groups/delete-test.js b/ui/admin/tests/acceptance/managed-groups/delete-test.js index db30cefff..86763f729 100644 --- a/ui/admin/tests/acceptance/managed-groups/delete-test.js +++ b/ui/admin/tests/acceptance/managed-groups/delete-test.js @@ -4,32 +4,47 @@ */ 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 { authenticateSession } from 'ember-simple-auth/test-support'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { Response } from 'miragejs'; -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 | managed-groups | delete', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let getManagedGroupCount; + + const DELETE_DROPDOWN_SELECTOR = + '.rose-layout-page-actions .rose-dropdown-button-danger'; + const ERROR_MSG_SELECTOR = '.rose-notification-body'; + const instances = { scopes: { global: null, org: null, }, authMethod: null, + ldapAuthMethod: null, managedGroup: null, + ldapManagedGroup: null, }; + const urls = { orgScope: null, authMethods: null, + authMethod: null, + ldapAuthMethod: null, managedGroups: null, - newManagedGroup: null, + ldapManagedGroups: null, managedGroup: null, + ldapManagedGroup: null, }; hooks.beforeEach(function () { @@ -43,36 +58,54 @@ module('Acceptance | managed-groups | delete', function (hooks) { scope: instances.scopes.org, type: TYPE_AUTH_METHOD_OIDC, }); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); instances.managedGroup = this.server.create('managed-group', { scope: instances.scopes.org, authMethod: instances.authMethod, }); + instances.ldapManagedGroup = this.server.create('managed-group', { + scope: instances.scopes.org, + authMethod: instances.ldapAuthMethod, + }); // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; urls.managedGroups = `${urls.authMethod}/managed-groups`; - urls.newManagedGroup = `${urls.managedGroups}/new`; - // TODO refactor in the rest of the test this url + urls.ldapManagedGroups = `${urls.ldapAuthMethod}/managed-groups`; urls.managedGroup = `${urls.managedGroups}/${instances.managedGroup.id}`; - }); + urls.ldapManagedGroup = `${urls.ldapManagedGroups}/${instances.ldapManagedGroup.id}`; - hooks.afterEach(async function () { - const notification = find('.rose-notification'); - if (notification) { - await click('.rose-notification-dismiss'); - } + getManagedGroupCount = () => + this.server.schema.managedGroups.all().models.length; }); test('User can delete a managed group', async function (assert) { - assert.expect(1); - const managedGroupsCount = this.server.db.managedGroups.length; - await visit(urls.managedGroup); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); - assert.strictEqual( - this.server.db.managedGroups.length, - managedGroupsCount - 1 - ); + assert.expect(2); + const managedGroupsCount = getManagedGroupCount(); + await visit(urls.managedGroups); + + await click(`[href="${urls.managedGroup}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + + assert.strictEqual(getManagedGroupCount(), managedGroupsCount - 1); + assert.strictEqual(currentURL(), urls.managedGroups); + }); + + test('User can delete a ldap managed group', async function (assert) { + assert.expect(2); + const managedGroupsCount = getManagedGroupCount(); + await visit(urls.ldapManagedGroups); + + await click(`[href="${urls.ldapManagedGroup}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + + assert.strictEqual(getManagedGroupCount(), managedGroupsCount - 1); + assert.strictEqual(currentURL(), urls.ldapManagedGroups); }); test('User cannot delete a managed-group without proper authorization', async function (assert) { @@ -81,14 +114,28 @@ module('Acceptance | managed-groups | delete', function (hooks) { instances.managedGroup.authorized_actions.filter( (item) => item !== 'delete' ); - await visit(urls.managedGroup); - assert.notOk( - find('.rose-layout-page-actions .rose-dropdown-button-danger') - ); + await visit(urls.managedGroups); + + await click(`[href="${urls.managedGroup}"]`); + + assert.dom(DELETE_DROPDOWN_SELECTOR).doesNotExist(); + }); + + test('User cannot delete a ldap managed-group without proper authorization', async function (assert) { + assert.expect(1); + instances.ldapManagedGroup.authorized_actions = + instances.ldapManagedGroup.authorized_actions.filter( + (item) => item !== 'delete' + ); + await visit(urls.ldapManagedGroups); + + await click(`[href="${urls.ldapManagedGroup}"]`); + + assert.dom(DELETE_DROPDOWN_SELECTOR).doesNotExist(); }); test('Errors are displayed when delete on managed group fails', async function (assert) { - assert.expect(1); + assert.expect(2); this.server.del('/managed-groups/:id', () => { return new Response( 490, @@ -100,13 +147,36 @@ module('Acceptance | managed-groups | delete', function (hooks) { } ); }); - await visit(urls.managedGroup); - await click('.rose-layout-page-actions .rose-dropdown-button-danger'); + await visit(urls.managedGroups); + + await click(`[href="${urls.managedGroup}"]`); + 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.'); + assert.strictEqual(currentURL(), urls.managedGroup); + }); + + test('Errors are displayed when delete on ldap managed group fails', async function (assert) { + assert.expect(2); + this.server.del('/managed-groups/:id', () => { + return new Response( + 490, + {}, + { + status: 490, + code: 'error', + message: 'Oops.', + } + ); + }); + await visit(urls.ldapManagedGroups); + + await click(`[href="${urls.ldapManagedGroup}"]`); + await click(DELETE_DROPDOWN_SELECTOR); + await a11yAudit(); + + assert.dom(ERROR_MSG_SELECTOR).hasText('Oops.'); + assert.strictEqual(currentURL(), urls.ldapManagedGroup); }); }); diff --git a/ui/admin/tests/acceptance/managed-groups/list-test.js b/ui/admin/tests/acceptance/managed-groups/list-test.js index 6b33317ad..7197ab4c8 100644 --- a/ui/admin/tests/acceptance/managed-groups/list-test.js +++ b/ui/admin/tests/acceptance/managed-groups/list-test.js @@ -4,30 +4,39 @@ */ import { module, test } from 'qunit'; -import { visit, click, find } from '@ember/test-helpers'; +import { visit, click } from '@ember/test-helpers'; import { setupApplicationTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; import { authenticateSession } from 'ember-simple-auth/test-support'; -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 | managed-groups | list', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let featuresService; + const instances = { scopes: { global: null, org: null, }, authMethod: null, + ldapAuthMethod: null, managedGroup: null, }; const urls = { orgScope: null, authMethods: null, + authMethod: null, + ldapAuthMethod: null, managedGroups: null, + ldapManagedGroups: null, newManagedGroup: null, - managedGroup: null, + newLdapManagedGroup: null, }; hooks.beforeEach(function () { @@ -41,47 +50,106 @@ module('Acceptance | managed-groups | list', function (hooks) { scope: instances.scopes.org, type: TYPE_AUTH_METHOD_OIDC, }); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); instances.managedGroup = this.server.create('managed-group', { scope: instances.scopes.org, authMethod: instances.authMethod, }); + // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; urls.managedGroups = `${urls.authMethod}/managed-groups`; + urls.ldapManagedGroups = `${urls.ldapAuthMethod}/managed-groups`; urls.newManagedGroup = `${urls.managedGroups}/new`; - urls.managedGroup = `${urls.managedGroups}/${instances.authMethod.id}`; - }); - - hooks.afterEach(async function () { - const notification = find('.rose-notification'); - if (notification) { - await click('.rose-notification-dismiss'); - } + urls.newLdapManagedGroup = `${urls.ldapManagedGroups}/new`; + featuresService = this.owner.lookup('service:features'); }); test('User can navigate to managed groups with proper authorization', async function (assert) { - assert.expect(2); - await visit(urls.authMethod); - assert.ok( + assert.expect(3); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.true( instances.authMethod.authorized_collection_actions[ 'managed-groups' ].includes('list') ); - assert.ok(find(`[href="${urls.managedGroups}"]`)); + assert.true( + instances.authMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('create') + ); + assert.dom(`[href="${urls.managedGroups}"]`).exists(); + }); + + test('User can navigate to ldap managed groups with proper authorization', async function (assert) { + assert.expect(3); + featuresService.enable('ldap-auth-methods'); + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.true( + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('list') + ); + assert.true( + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('create') + ); + assert.dom(`[href="${urls.ldapManagedGroups}"]`).exists(); }); test('User cannot navigate to index without either list or create actions', async function (assert) { - assert.expect(2); + assert.expect(3); instances.authMethod.authorized_collection_actions['managed-groups'] = []; - await visit(urls.authMethod); - assert.notOk( + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.false( instances.authMethod.authorized_collection_actions[ 'managed-groups' ].includes('list') ); - assert.notOk(find(`[href="${urls.managedGroups}"]`)); + assert.false( + instances.authMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('create') + ); + assert.dom(`[href="${urls.managedGroups}"]`).doesNotExist(); + }); + + test('User cannot navigate to ldap managed group index without either list or create actions', async function (assert) { + assert.expect(3); + featuresService.enable('ldap-auth-methods'); + instances.ldapAuthMethod.authorized_collection_actions['managed-groups'] = + []; + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.false( + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('list') + ); + assert.false( + instances.ldapAuthMethod.authorized_collection_actions[ + 'managed-groups' + ].includes('create') + ); + assert.dom(`[href="${urls.ldapManagedGroups}"]`).doesNotExist(); }); test('User can navigate to index with only create action', async function (assert) { @@ -89,10 +157,25 @@ module('Acceptance | managed-groups | list', function (hooks) { instances.authMethod.authorized_collection_actions['managed-groups'] = [ 'create', ]; - await visit(urls.authMethod); - assert.ok(find(`[href="${urls.managedGroups}"]`)); - assert.ok( - find(`.rose-layout-page-actions [href="${urls.newManagedGroup}"]`) - ); + await visit(urls.authMethods); + + await click(`[href="${urls.authMethod}"]`); + + assert.dom(`[href="${urls.managedGroups}"]`).exists(); + assert.dom(`[href="${urls.newManagedGroup}"]`).exists(); + }); + + test('User can navigate to ldap managed groups index with only create action', async function (assert) { + assert.expect(2); + featuresService.enable('ldap-auth-methods'); + instances.authMethod.authorized_collection_actions['managed-groups'] = [ + 'create', + ]; + await visit(urls.authMethods); + + await click(`[href="${urls.ldapAuthMethod}"]`); + + assert.dom(`[href="${urls.ldapManagedGroups}"]`).exists(); + assert.dom(`[href="${urls.newLdapManagedGroup}"]`).exists(); }); }); diff --git a/ui/admin/tests/acceptance/managed-groups/read-test.js b/ui/admin/tests/acceptance/managed-groups/read-test.js index 67f97f853..8a36675df 100644 --- a/ui/admin/tests/acceptance/managed-groups/read-test.js +++ b/ui/admin/tests/acceptance/managed-groups/read-test.js @@ -4,12 +4,15 @@ */ import { module, test } from 'qunit'; -import { visit, click, find, currentURL } 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 { authenticateSession } from 'ember-simple-auth/test-support'; import a11yAudit from 'ember-a11y-testing/test-support/audit'; -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 | managed-groups | read', function (hooks) { setupApplicationTest(hooks); @@ -21,14 +24,19 @@ module('Acceptance | managed-groups | read', function (hooks) { org: null, }, authMethod: null, + ldapAuthMethod: null, managedGroup: null, + ldapManagedGroup: null, }; const urls = { orgScope: null, authMethods: null, + authMethod: null, + ldapAuthMethod: null, managedGroups: null, - newManagedGroup: null, + ldapManagedGroups: null, managedGroup: null, + ldapManagedGroup: null, }; hooks.beforeEach(function () { @@ -42,42 +50,72 @@ module('Acceptance | managed-groups | read', function (hooks) { scope: instances.scopes.org, type: TYPE_AUTH_METHOD_OIDC, }); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); instances.managedGroup = this.server.create('managed-group', { scope: instances.scopes.org, authMethod: instances.authMethod, }); + instances.ldapManagedGroup = this.server.create('managed-group', { + scope: instances.scopes.org, + authMethod: instances.ldapAuthMethod, + }); // Generate route URLs for resources urls.orgScope = `/scopes/${instances.scopes.org.id}`; urls.authMethods = `${urls.orgScope}/auth-methods`; urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + urls.ldapAuthMethod = `${urls.authMethods}/${instances.ldapAuthMethod.id}`; urls.managedGroups = `${urls.authMethod}/managed-groups`; - urls.newManagedGroup = `${urls.managedGroups}/new`; - // TODO refactor in the rest of the test this url + urls.ldapManagedGroups = `${urls.ldapAuthMethod}/managed-groups`; urls.managedGroup = `${urls.managedGroups}/${instances.managedGroup.id}`; - }); - - hooks.afterEach(async function () { - const notification = find('.rose-notification'); - if (notification) { - await click('.rose-notification-dismiss'); - } + urls.ldapManagedGroup = `${urls.ldapManagedGroups}/${instances.ldapManagedGroup.id}`; }); test('User can navigate to a managed group form', async function (assert) { assert.expect(1); await visit(urls.managedGroups); - await click('main tbody .rose-table-header-cell:nth-child(1) a'); + + await click(`[href="${urls.managedGroup}"]`); await a11yAudit(); + assert.strictEqual(currentURL(), urls.managedGroup); }); + test('User can navigate to a ldap managed group form', async function (assert) { + assert.expect(1); + await visit(urls.ldapManagedGroups); + + await click(`[href="${urls.ldapManagedGroup}"]`); + await a11yAudit(); + + assert.strictEqual(currentURL(), urls.ldapManagedGroup); + }); + test('User cannot navigate to a managed group form without proper authorization', async function (assert) { assert.expect(1); instances.managedGroup.authorized_actions = instances.managedGroup.authorized_actions.filter( (item) => item !== 'read' ); - await visit(urls.managedGroups); - assert.notOk(find('main tbody .rose-table-header-cell:nth-child(1) a')); + await visit(urls.authMethod); + + await click(`[href="${urls.managedGroups}"]`); + + assert.dom(`[href="${urls.managedGroup}"]`).doesNotExist(); + }); + + test('User cannot navigate to a ldap managed group form without proper authorization', async function (assert) { + assert.expect(1); + instances.ldapManagedGroup.authorized_actions = + instances.ldapManagedGroup.authorized_actions.filter( + (item) => item !== 'read' + ); + await visit(urls.ldapAuthMethod); + + await click(`[href="${urls.ldapManagedGroups}"]`); + + assert.dom(`[href="${urls.ldapManagedGroup}"]`).doesNotExist(); }); });