diff --git a/addons/api/addon/abilities/auth-method.js b/addons/api/addon/abilities/auth-method.js new file mode 100644 index 000000000..744f93ac7 --- /dev/null +++ b/addons/api/addon/abilities/auth-method.js @@ -0,0 +1,20 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import ModelAbility from './model'; +/** + * Provides abilities for auth method. + */ +export default class AuthMethodAbility extends ModelAbility { + // =permissions + + /** + * Only "known" auth method types may be read. + * @type {boolean} + */ + get canRead() { + return !this.model.isUnknown && this.hasAuthorizedAction('read'); + } +} diff --git a/addons/api/addon/models/auth-method.js b/addons/api/addon/models/auth-method.js index dc519cf47..8599160f1 100644 --- a/addons/api/addon/models/auth-method.js +++ b/addons/api/addon/models/auth-method.js @@ -6,6 +6,16 @@ import GeneratedAuthMethodModel from '../generated/models/auth-method'; import { equal } from '@ember/object/computed'; +export const TYPE_AUTH_METHOD_PASSWORD = 'password'; +export const TYPE_AUTH_METHOD_OIDC = 'oidc'; +export const TYPE_AUTH_METHOD_LDAP = 'ldap'; + +export const TYPES_AUTH_METHOD = Object.freeze([ + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +]); + /** * Enum options per auth method type and field. */ @@ -44,14 +54,28 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel { * @type {boolean} */ get isPassword() { - return this.type === 'password'; + return this.type === TYPE_AUTH_METHOD_PASSWORD; } /** * @type {boolean} */ get isOIDC() { - return this.type === 'oidc'; + return this.type === TYPE_AUTH_METHOD_OIDC; + } + + /** + * @type {boolean} + */ + get isLDAP() { + return this.type === TYPE_AUTH_METHOD_LDAP; + } + + /** + * @type {boolean} + */ + get isUnknown() { + return !TYPES_AUTH_METHOD.includes(this.type); } /** diff --git a/addons/api/addon/serializers/auth-method.js b/addons/api/addon/serializers/auth-method.js index d21f54cff..01488e4e6 100644 --- a/addons/api/addon/serializers/auth-method.js +++ b/addons/api/addon/serializers/auth-method.js @@ -4,6 +4,8 @@ */ import ApplicationSerializer from './application'; +import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; + export default class AuthMethodSerializer extends ApplicationSerializer { // =methods @@ -15,7 +17,7 @@ export default class AuthMethodSerializer extends ApplicationSerializer { */ serialize(snapshot) { switch (snapshot.record.type) { - case 'oidc': + case TYPE_AUTH_METHOD_OIDC: return this.serializeOIDC(...arguments); default: return this.serializeDefault(...arguments); diff --git a/addons/api/mirage/factories/auth-method.js b/addons/api/mirage/factories/auth-method.js index 96589133f..dee5481a3 100644 --- a/addons/api/mirage/factories/auth-method.js +++ b/addons/api/mirage/factories/auth-method.js @@ -8,7 +8,10 @@ import { trait } from 'miragejs'; import permissions from '../helpers/permissions'; import generateId from '../helpers/id'; -const types = ['password', 'oidc']; +import { + TYPE_AUTH_METHOD_OIDC, + TYPES_AUTH_METHOD, +} from 'api/models/auth-method'; export default factory.extend({ authorized_actions: () => @@ -29,7 +32,7 @@ export default factory.extend({ id: () => generateId('am_'), // Cycle through available types - type: (i) => types[i % types.length], + type: (i) => TYPES_AUTH_METHOD[i % TYPES_AUTH_METHOD.length], /** * Adds accounts (with associated users) to auth method and managed groups. @@ -43,7 +46,7 @@ export default factory.extend({ user.update({ accountIds: [id] }); }); - if (type === 'oidc') { + if (type === TYPE_AUTH_METHOD_OIDC) { server .createList('managed-group', 2, { scope, authMethod }) .map((managedGroup) => { diff --git a/addons/api/mirage/generated/factories/auth-method.js b/addons/api/mirage/generated/factories/auth-method.js index bc6b5c79b..d0cdd71c3 100644 --- a/addons/api/mirage/generated/factories/auth-method.js +++ b/addons/api/mirage/generated/factories/auth-method.js @@ -6,11 +6,16 @@ import { Factory } from 'miragejs'; import { faker } from '@faker-js/faker'; +import { + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, +} from 'api/models/auth-method'; + /** * GeneratedAuthMethodModelFactory */ export default Factory.extend({ - type: 'password', + type: TYPE_AUTH_METHOD_PASSWORD, name: () => faker.random.words(), description: () => faker.random.words(), created_time: () => faker.date.recent(), @@ -20,7 +25,7 @@ export default Factory.extend({ attributes: function () { let attrs; switch (this.type) { - case 'oidc': + case TYPE_AUTH_METHOD_OIDC: attrs = { api_url_prefix: 'http://127.0.0.1:9200', callback_url: diff --git a/addons/api/tests/unit/models/auth-method-test.js b/addons/api/tests/unit/models/auth-method-test.js index 1e084b9ae..a908d2280 100644 --- a/addons/api/tests/unit/models/auth-method-test.js +++ b/addons/api/tests/unit/models/auth-method-test.js @@ -6,13 +6,23 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +} from 'api/models/auth-method'; module('Unit | Model | auth method', function (hooks) { setupTest(hooks); setupMirage(hooks); + let store; + + hooks.beforeEach(function () { + store = this.owner.lookup('service:store'); + }); + test('it exists', function (assert) { - let store = this.owner.lookup('service:store'); let model = store.createRecord('auth-method', {}); assert.ok(model); }); @@ -32,13 +42,12 @@ module('Unit | Model | auth method', function (hooks) { return { id: '123abc' }; } ); - const store = this.owner.lookup('service:store'); store.push({ data: { id: '123abc', type: 'auth-method', attributes: { - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, state: 'foobar', version: 1, scope: { @@ -51,4 +60,72 @@ module('Unit | Model | auth method', function (hooks) { const model = store.peekRecord('auth-method', '123abc'); await model.changeState('foobar'); }); + + test('it has isPassword property and returns the expected values', async function (assert) { + assert.expect(3); + const modelA = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_PASSWORD, + }); + const modelB = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_OIDC, + }); + const modelC = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_LDAP, + }); + assert.true(modelA.isPassword); + assert.false(modelB.isPassword); + assert.false(modelC.isPassword); + }); + + test('it has isOIDC property and returns the expected values', async function (assert) { + assert.expect(3); + const modelA = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_OIDC, + }); + const modelB = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_PASSWORD, + }); + const modelC = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_LDAP, + }); + assert.true(modelA.isOIDC); + assert.false(modelB.isOIDC); + assert.false(modelC.isOIDC); + }); + + test('it has isLDAP property and returns the expected values', async function (assert) { + assert.expect(3); + const modelA = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_LDAP, + }); + const modelB = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_PASSWORD, + }); + const modelC = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_OIDC, + }); + assert.true(modelA.isLDAP); + assert.false(modelB.isLDAP); + assert.false(modelC.isLDAP); + }); + + test('it has isUnknown property and returns the expected values', async function (assert) { + assert.expect(4); + const modelA = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_LDAP, + }); + const modelB = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_PASSWORD, + }); + const modelC = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_OIDC, + }); + const modelD = store.createRecord('auth-method', { + type: 'no-such-type', + }); + assert.false(modelA.isUnknown); + assert.false(modelB.isUnknown); + assert.false(modelC.isUnknown); + assert.true(modelD.isUnknown); + }); }); diff --git a/addons/api/tests/unit/serializers/auth-method-test.js b/addons/api/tests/unit/serializers/auth-method-test.js index 4ecf4d08c..f9989a5f3 100644 --- a/addons/api/tests/unit/serializers/auth-method-test.js +++ b/addons/api/tests/unit/serializers/auth-method-test.js @@ -7,6 +7,11 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, +} from 'api/models/auth-method'; + module('Unit | Serializer | auth method', function (hooks) { setupTest(hooks); setupMirage(hooks); @@ -21,7 +26,9 @@ module('Unit | Serializer | auth method', function (hooks) { test('it serializes password records', function (assert) { assert.expect(2); let store = this.owner.lookup('service:store'); - let record = store.createRecord('auth-method', { type: 'password' }); + let record = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_PASSWORD, + }); let serializedRecord = record.serialize(); @@ -36,7 +43,7 @@ module('Unit | Serializer | auth method', function (hooks) { assert.expect(1); let store = this.owner.lookup('service:store'); let record = store.createRecord('auth-method', { - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, name: 'OIDC Auth Method', state: 'foo', account_claim_maps: [{ from: 'foo', to: 'bar' }], @@ -62,7 +69,7 @@ module('Unit | Serializer | auth method', function (hooks) { let serializedRecord = record.serialize(); assert.deepEqual(serializedRecord, { - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, name: 'OIDC Auth Method', description: null, attributes: { @@ -87,7 +94,7 @@ module('Unit | Serializer | auth method', function (hooks) { const store = this.owner.lookup('service:store'); const serializer = store.serializerFor('auth-method'); const record = store.createRecord('auth-method', { - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, attributes: { state: 'foo', }, @@ -175,7 +182,7 @@ module('Unit | Serializer | auth method', function (hooks) { max_age: maxAge, }, version: 1, - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, id: 'oidc123', })); diff --git a/ui/admin/app/abilities/auth-method.js b/ui/admin/app/abilities/auth-method.js new file mode 100644 index 000000000..ebbf9b0cd --- /dev/null +++ b/ui/admin/app/abilities/auth-method.js @@ -0,0 +1,21 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import AuthMethodAbility from 'api/abilities/auth-method'; + +export default class OverrideAuthMethodAbility extends AuthMethodAbility { + /** + * These override abilities are temporary and just part of Phase 1 + * of LDAP auth method implementation. + */ + + get canRead() { + return !this.model.isLDAP && super.canRead; + } + + get canMakePrimary() { + return !this.model.isLDAP; + } +} diff --git a/ui/admin/app/routes/scopes/scope/auth-methods.js b/ui/admin/app/routes/scopes/scope/auth-methods.js index e4c2b9312..b5522e3db 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods.js @@ -10,6 +10,7 @@ import { loading } from 'ember-loading'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; import { resourceFilter } from 'core/decorators/resource-filter'; +import { TYPES_AUTH_METHOD } from 'api/models/auth-method'; export default class ScopesScopeAuthMethodsRoute extends Route { // =services @@ -23,7 +24,7 @@ export default class ScopesScopeAuthMethodsRoute extends Route { // =attributes - @resourceFilter({ allowed: ['password', 'oidc'] }) type; + @resourceFilter({ allowed: TYPES_AUTH_METHOD }) type; // =methods diff --git a/ui/admin/app/routes/scopes/scope/authenticate/method.js b/ui/admin/app/routes/scopes/scope/authenticate/method.js index f6532a008..f7cc6b680 100644 --- a/ui/admin/app/routes/scopes/scope/authenticate/method.js +++ b/ui/admin/app/routes/scopes/scope/authenticate/method.js @@ -9,6 +9,11 @@ import { getOwner } from '@ember/application'; import { action } from '@ember/object'; import { loading } from 'ember-loading'; import { notifyError } from 'core/decorators/notify'; +import { + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +} from 'api/models/auth-method'; export default class ScopesScopeAuthenticateMethodRoute extends Route { // =services @@ -62,8 +67,8 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route { const authenticatorName = `authenticator:${authMethod.type}`; const requestCookies = false; switch (authMethod.type) { - case 'password': - case 'ldap': + case TYPE_AUTH_METHOD_PASSWORD: + case TYPE_AUTH_METHOD_LDAP: await this.session.authenticate( authenticatorName, creds, @@ -72,7 +77,7 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route { ); this.router.transitionTo('index'); break; - case 'oidc': + case TYPE_AUTH_METHOD_OIDC: await this.startOIDCAuthentication(authenticatorName, { scope, authMethod, diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs index 2ecdc0458..31dac7bb6 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/index.hbs @@ -120,7 +120,7 @@ {{#each @model as |authMethod|}} - {{#if (can 'read model' authMethod)}} + {{#if (can 'read auth-method' authMethod)}} - - {{#unless authMethod.isPrimary}} - - {{t 'resources.auth-method.actions.make-primary'}} - - {{/unless}} - {{#if authMethod.isPrimary}} - - {{t 'resources.auth-method.actions.remove-as-primary'}} - - {{/if}} - + {{#if (can 'makePrimary auth-method' authMethod)}} + + {{#unless authMethod.isPrimary}} + + {{t 'resources.auth-method.actions.make-primary'}} + + {{/unless}} + {{#if authMethod.isPrimary}} + + {{t 'resources.auth-method.actions.remove-as-primary'}} + + {{/if}} + + {{/if}} {{/each}} diff --git a/ui/admin/tests/acceptance/auth-methods/oidc-test.js b/ui/admin/tests/acceptance/auth-methods/oidc-test.js index 7262e3f2a..00c62238f 100644 --- a/ui/admin/tests/acceptance/auth-methods/oidc-test.js +++ b/ui/admin/tests/acceptance/auth-methods/oidc-test.js @@ -23,6 +23,8 @@ import { //invalidateSession, } from 'ember-simple-auth/test-support'; +import { TYPE_AUTH_METHOD_OIDC } from 'api/models/auth-method'; + module('Acceptance | auth methods | oidc', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -49,7 +51,7 @@ module('Acceptance | auth methods | oidc', function (hooks) { }); instances.authMethod = this.server.create('auth-method', { scope: instances.scopes.org, - type: 'oidc', + type: TYPE_AUTH_METHOD_OIDC, }); urls.orgScope = `/scopes/${instances.scopes.org.id}`; diff --git a/ui/admin/tests/acceptance/auth-methods/read-test.js b/ui/admin/tests/acceptance/auth-methods/read-test.js index 29ea63b75..72ff3c2b9 100644 --- a/ui/admin/tests/acceptance/auth-methods/read-test.js +++ b/ui/admin/tests/acceptance/auth-methods/read-test.js @@ -4,7 +4,7 @@ */ import { module, test } from 'qunit'; -import { visit, currentURL, click, find } from '@ember/test-helpers'; +import { visit, currentURL, click } 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'; @@ -14,23 +14,44 @@ import { // //currentSession, // //invalidateSession, } from 'ember-simple-auth/test-support'; +import { + TYPE_AUTH_METHOD_LDAP, + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_PASSWORD, +} from 'api/models/auth-method'; module('Acceptance | auth methods | read', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); + const AUTH_LINK_SELECTOR = + 'main tbody .rose-table-header-cell:nth-child(1) a'; + const AUTH_TYPE_SELECTOR = + 'tbody .rose-table-row:nth-child(1) .rose-table-cell'; + const AUTH_ACTIONS_SELECTOR = + 'tbody .rose-table-row:nth-child(1) .rose-table-cell:last-child .rose-dropdown'; + const LDAP_AUTH_LINK_SELECTOR = 'tbody .rose-table-row:nth-child(2) a'; + const LDAP_AUTH_TYPE_SELECTOR = + 'tbody .rose-table-row:nth-child(2) .rose-table-cell'; + const LDAP_AUTH_ACTIONS_SELECTOR = + 'tbody .rose-table-row:nth-child(2) .rose-table-cell:last-child .rose-dropdown'; const instances = { scopes: { global: null, org: null, }, - authMethod: null, + passwordAuthMethodOrg: null, + oidcAuthMethodGlobal: null, + ldapAuthMethod: null, }; + const urls = { + globalScope: null, orgScope: null, - authMethods: null, - newAuthMethod: null, - authMethod: null, + orgAuthMethods: null, + globalAuthMethods: null, + passwordAuthMethodOrg: null, + oidcAuthMethodGlobal: null, }; hooks.beforeEach(function () { @@ -41,37 +62,126 @@ module('Acceptance | auth methods | read', function (hooks) { type: 'org', scope: { id: 'global', type: 'global' }, }); - instances.authMethod = this.server.create('auth-method', { + instances.passwordAuthMethodOrg = this.server.create('auth-method', { scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_PASSWORD, + }); + instances.oidcAuthMethodGlobal = this.server.create('auth-method', { + scope: instances.scopes.global, + type: TYPE_AUTH_METHOD_OIDC, }); - // Generate route URLs for resources + urls.globalScope = `/scopes/global`; 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.orgAuthMethods = `${urls.orgScope}/auth-methods`; + urls.globalAuthMethods = `${urls.globalScope}/auth-methods`; + urls.passwordAuthMethodOrg = `${urls.orgAuthMethods}/${instances.passwordAuthMethodOrg.id}`; + urls.oidcAuthMethodGlobal = `${urls.globalAuthMethods}/${instances.oidcAuthMethodGlobal.id}`; }); - test('visiting auth methods', async function (assert) { + test('visiting auth methods in org scope', async function (assert) { assert.expect(1); - await visit(urls.authMethods); + await visit(urls.orgScope); + + await click(`[href="${urls.orgAuthMethods}"]`); await a11yAudit(); - assert.strictEqual(currentURL(), urls.authMethods); + + assert.strictEqual(currentURL(), urls.orgAuthMethods); }); - test('can navigate to an auth method form', async function (assert) { + test('visiting auth methods in global scope', async function (assert) { assert.expect(1); - await visit(urls.authMethods); - await click('main tbody .rose-table-header-cell:nth-child(1) a'); + await visit(urls.globalScope); + + await click(`[href="${urls.globalAuthMethods}"]`); await a11yAudit(); - assert.strictEqual(currentURL(), urls.authMethod); + + assert.strictEqual(currentURL(), urls.globalAuthMethods); }); - test('cannot navigate to an auth method form without proper authorization', async function (assert) { + test('can navigate to an auth method form in org scope', async function (assert) { + assert.expect(3); + await visit(urls.orgScope); + + await click(`[href="${urls.orgAuthMethods}"]`); + + assert.dom(AUTH_TYPE_SELECTOR).hasText('Password'); + assert.dom(AUTH_ACTIONS_SELECTOR).exists(); + + await click(AUTH_LINK_SELECTOR); + await a11yAudit(); + + assert.strictEqual(currentURL(), urls.passwordAuthMethodOrg); + }); + + test('can navigate to an auth method form in global scope', async function (assert) { + assert.expect(3); + await visit(urls.globalScope); + + await click(`[href="${urls.globalAuthMethods}"]`); + + assert.dom(AUTH_TYPE_SELECTOR).hasText('OIDC'); + assert.dom(AUTH_ACTIONS_SELECTOR).exists(); + + await click(AUTH_LINK_SELECTOR); + await a11yAudit(); + + assert.strictEqual(currentURL(), urls.oidcAuthMethodGlobal); + }); + + test('cannot navigate to an auth method form without proper authorization in org scope', async function (assert) { assert.expect(1); - instances.authMethod.authorized_actions = - instances.authMethod.authorized_actions.filter((item) => item !== 'read'); - await visit(urls.authMethod); - assert.notOk(find('main tbody .rose-table-header-cell:nth-child(1) a')); + instances.passwordAuthMethodOrg.authorized_actions = + instances.passwordAuthMethodOrg.authorized_actions.filter( + (item) => item !== 'read' + ); + await visit(urls.orgScope); + + await click(`[href="${urls.orgAuthMethods}"]`); + + assert.dom(AUTH_LINK_SELECTOR).doesNotExist(); + }); + + test('cannot navigate to an auth method form without proper authorization in global scope', async function (assert) { + assert.expect(1); + instances.oidcAuthMethodGlobal.authorized_actions = + instances.oidcAuthMethodGlobal.authorized_actions.filter( + (item) => item !== 'read' + ); + await visit(urls.globalScope); + + await click(`[href="${urls.globalAuthMethods}"]`); + + assert.dom(AUTH_LINK_SELECTOR).doesNotExist(); + }); + + test('cannot navigate to an ldap auth method form in global scope', async function (assert) { + assert.expect(3); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.global, + type: TYPE_AUTH_METHOD_LDAP, + }); + await visit(urls.globalScope); + + await click(`[href="${urls.globalAuthMethods}"]`); + + assert.dom(LDAP_AUTH_LINK_SELECTOR).doesNotExist(); + assert.dom(LDAP_AUTH_TYPE_SELECTOR).hasText('LDAP'); + assert.dom(LDAP_AUTH_ACTIONS_SELECTOR).doesNotExist(); + }); + + test('cannot navigate to an ldap auth method form in org scope', async function (assert) { + assert.expect(3); + instances.ldapAuthMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: TYPE_AUTH_METHOD_LDAP, + }); + await visit(urls.orgScope); + + await click(`[href="${urls.orgAuthMethods}"]`); + + assert.dom(LDAP_AUTH_LINK_SELECTOR).doesNotExist(); + assert.dom(LDAP_AUTH_TYPE_SELECTOR).hasText('LDAP'); + assert.dom(LDAP_AUTH_ACTIONS_SELECTOR).doesNotExist(); }); }); diff --git a/ui/admin/tests/unit/abilities/auth-method-test.js b/ui/admin/tests/unit/abilities/auth-method-test.js new file mode 100644 index 000000000..22cfa8699 --- /dev/null +++ b/ui/admin/tests/unit/abilities/auth-method-test.js @@ -0,0 +1,75 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: MPL-2.0 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { + TYPE_AUTH_METHOD_LDAP, + TYPE_AUTH_METHOD_OIDC, +} from 'api/models/auth-method'; + +module('Unit | Abilities | auth-method', function (hooks) { + setupTest(hooks); + + let canService; + let store; + + hooks.beforeEach(function () { + canService = this.owner.lookup('service:can'); + store = this.owner.lookup('service:store'); + }); + + test('cannot read LDAP auth-method when authorized', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + authorized_actions: ['read'], + type: TYPE_AUTH_METHOD_LDAP, + }); + assert.false(canService.can('read auth-method', authMethod)); + }); + + test('cannot read LDAP auth-method when unauthorized', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + authorized_actions: [], + type: TYPE_AUTH_METHOD_LDAP, + }); + assert.false(canService.can('read auth-method', authMethod)); + }); + + test('can read non-LDAP auth-method when authorized', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + authorized_actions: ['read'], + type: TYPE_AUTH_METHOD_OIDC, + }); + assert.true(canService.can('read auth-method', authMethod)); + }); + + test('cannot read non-LDAP auth-method when unauthorized', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + authorized_actions: [], + type: TYPE_AUTH_METHOD_OIDC, + }); + assert.false(canService.can('read auth-method', authMethod)); + }); + + test('cannot make LDAP auth-method primary', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_LDAP, + }); + assert.false(canService.can('makePrimary auth-method', authMethod)); + }); + + test('can make non-LDAP auth-method primary', function (assert) { + assert.expect(1); + const authMethod = store.createRecord('auth-method', { + type: TYPE_AUTH_METHOD_OIDC, + }); + assert.true(canService.can('makePrimary auth-method', authMethod)); + }); +});