diff --git a/addons/api/mirage/factories/credential-store.js b/addons/api/mirage/factories/credential-store.js index 15ac2b200..e84c494ca 100644 --- a/addons/api/mirage/factories/credential-store.js +++ b/addons/api/mirage/factories/credential-store.js @@ -13,6 +13,11 @@ export default factory.extend({ 'update', 'delete', ], + authorized_collection_actions: () => { + return { + 'credential-libraries': ['create', 'list'], + }; + }, id: (i) => `credential-store-id-${i}`, type: (i) => types[i % types.length], diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js index 84684ecfd..a58c7cc52 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries.js @@ -3,19 +3,28 @@ import { action } from '@ember/object'; import loading from 'ember-loading/decorator'; import { confirm } from 'core/decorators/confirm'; import { notifySuccess, notifyError } from 'core/decorators/notify'; +import { inject as service } from '@ember/service'; export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesRoute extends Route { // =methods + @service can; /** * Loads all credential libraries under the current credential store. * @return {Promise{[CredentialLibraryModel]}} */ model() { - const { id: credential_store_id } = this.modelFor( + const scope = this.modelFor( 'scopes.scope.credential-stores.credential-store' ); - return this.store.query('credential-library', { credential_store_id }); + const { id: credential_store_id } = scope; + if ( + this.can.can('list collection', scope, { + collection: 'credential-libraries', + }) + ) { + return this.store.query('credential-library', { credential_store_id }); + } } // =actions diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js index 5b017ab54..ef7e6e66b 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credential-libraries/index.js @@ -1,3 +1,11 @@ import Route from '@ember/routing/route'; -export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesIndexRoute extends Route {} +export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesIndexRoute extends Route { + // =methods + + setupController(controller) { + const scope = this.modelFor('scopes.scope'); + super.setupController(...arguments); + controller.setProperties({ scope }); + } +} diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs index bf81de1ab..b264a425a 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/-actions.hbs @@ -3,12 +3,15 @@ @dropdownRight={{true}} as |dropdown| > - - {{t 'resources.credential-library.actions.create'}} - + {{#if (can 'create collection' @model collection='credential-libraries')}} + + + {{t 'resources.credential-library.actions.create'}} + + {{/if}} {{#if (can 'delete model' @model)}} {{t 'titles.details'}} - - {{t 'resources.credential-library.title_plural'}} - + {{#if (can 'navigate collection' @model collection='credential-libraries')}} + + + {{t 'resources.credential-library.title_plural'}} + + {{/if}} \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs index 2bb3101c2..75e691028 100644 --- a/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs +++ b/ui/admin/app/templates/scopes/scope/credential-stores/credential-store/credential-libraries/index.hbs @@ -53,7 +53,28 @@ as |message| > - {{t 'resources.credential-library.messages.none.description'}} + {{#if + (can 'list collection' this.scope collection='credential-libraries') + }} + {{! can list (at least): show default welcome message}} + {{t 'resources.credential-library.messages.none.description'}} + + {{else if (can + 'create collection' this.scope collection='credential-libraries' + )}} + {{! can create (only): show create-but-not-list welcome message}} + {{t + 'descriptions.create-but-not-list' + resource=(t 'resources.credential-library.title_plural') + }} + {{else}} + {{! can neither list nor create }} + {{t + 'descriptions.neither-list-nor-create' + resource=(t 'resources.credential-library.title_plural') + }} + {{/if}} + {{t 'resources.credential-store.description'}} - - - {{t 'titles.new'}} - + {{#if + (can + 'create collection' this.scope collection='credential-libraries' + ) + }} + + + {{t 'titles.new'}} + + {{/if}} + diff --git a/ui/admin/tests/acceptance/credential-libraries-test.js b/ui/admin/tests/acceptance/credential-library/create-test.js similarity index 89% rename from ui/admin/tests/acceptance/credential-libraries-test.js rename to ui/admin/tests/acceptance/credential-library/create-test.js index d4dcce482..58d663f2f 100644 --- a/ui/admin/tests/acceptance/credential-libraries-test.js +++ b/ui/admin/tests/acceptance/credential-library/create-test.js @@ -6,7 +6,7 @@ import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { authenticateSession } from 'ember-simple-auth/test-support'; import { Response } from 'miragejs'; -module('Acceptance | credential-libraries', function (hooks) { +module('Acceptance | credential-libraries | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -93,6 +93,20 @@ module('Acceptance | credential-libraries', function (hooks) { assert.equal(getCredentialLibraryCount(), count + 1); }); + test('Users cannot navigate to new credential library route without proper authorization', async function (assert) { + assert.expect(2); + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ] = []; + await visit(urls.credentialStore); + assert.notOk( + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ].includes('create') + ); + assert.notOk(find(`[href="${urls.credentialLibraries}"]`)); + }); + test('can cancel create a new credential library', async function (assert) { assert.expect(2); const count = getCredentialLibraryCount(); diff --git a/ui/admin/tests/acceptance/credential-library/list-test.js b/ui/admin/tests/acceptance/credential-library/list-test.js new file mode 100644 index 000000000..5ed0289cd --- /dev/null +++ b/ui/admin/tests/acceptance/credential-library/list-test.js @@ -0,0 +1,95 @@ +import { module, test } from 'qunit'; +import { visit, find } from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | credential-libraries | list', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + }; + + const urls = { + globalScope: null, + orgScope: null, + projectScope: null, + credentialStores: null, + credentialStore: null, + credentialLibrary: null, + credentialLibraries: null, + }; + + hooks.beforeEach(function () { + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.credentialStore = this.server.create('credential-store', { + scope: instances.scopes.project, + }); + instances.credentialLibrary = this.server.create('credential-library', { + scope: instances.scopes.project, + credentialStore: instances.credentialStore, + }); + // Generate route URLs for resources + urls.globalScope = `/scopes/global/scopes`; + urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; + urls.projectScope = `/scopes/${instances.scopes.project.id}`; + urls.credentialStores = `${urls.projectScope}/credential-stores`; + urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; + urls.credentialLibraries = `${urls.credentialStore}/credential-libraries`; + urls.credentialLibrary = `${urls.credentialLibraries}/${instances.credentialLibrary.id}`; + authenticateSession({}); + }); + + test('Users can navigate to users with proper authorization', async function (assert) { + assert.expect(2); + await visit(urls.credentialStore); + assert.ok( + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ].includes('list') + ); + assert.ok(find(`[href="${urls.credentialLibraries}"]`)); + }); + + test('Users cannot navigate to index without either list or create actions', async function (assert) { + assert.expect(2); + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ] = []; + await visit(urls.credentialStore); + assert.notOk( + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ].includes('list') + ); + assert.notOk(find(`[href="${urls.credentialLibraries}"]`)); + }); + + test('Users can navigate to index with only create action', async function (assert) { + assert.expect(1); + instances.credentialStore.authorized_collection_actions[ + 'credential-libraries' + ] = ['create']; + await visit(urls.credentialStore); + assert.ok(find(`[href="${urls.credentialLibraries}"]`)); + }); +}); diff --git a/ui/admin/tests/acceptance/credential-stores-test.js b/ui/admin/tests/acceptance/credential-stores-test.js deleted file mode 100644 index 54764b5a3..000000000 --- a/ui/admin/tests/acceptance/credential-stores-test.js +++ /dev/null @@ -1,112 +0,0 @@ -import { module, test } from 'qunit'; -import { visit, currentURL, find, 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 { Response } from 'miragejs'; - -module('Acceptance | credential-stores', function (hooks) { - setupApplicationTest(hooks); - setupMirage(hooks); - - let getCredentialStoresCount; - - const instances = { - scopes: { - global: null, - org: null, - project: null, - }, - }; - - const urls = { - globalScope: null, - orgScope: null, - projectScope: null, - credentialStores: null, - credentialStore: null, - unknownCredentialStore: null, - newCredentialStore: null, - }; - - hooks.beforeEach(function () { - // Generate resources - instances.scopes.global = this.server.create('scope', { id: 'global' }); - instances.scopes.org = this.server.create('scope', { - type: 'org', - scope: { id: 'global', type: 'global' }, - }); - instances.scopes.project = this.server.create('scope', { - type: 'project', - scope: { id: instances.scopes.org.id, type: 'org' }, - }); - instances.credentialStore = this.server.create('credential-store', { - scope: instances.scopes.project, - }); - // Generate route URLs for resources - urls.globalScope = `/scopes/global/scopes`; - urls.orgScope = `/scopes/${instances.scopes.org.id}/scopes`; - urls.projectScope = `/scopes/${instances.scopes.project.id}`; - urls.credentialStores = `${urls.projectScope}/credential-stores`; - urls.credentialStore = `${urls.credentialStores}/${instances.credentialStore.id}`; - urls.unknownCredentialStore = `${urls.credentialStores}/foo`; - urls.newCredentialStore = `${urls.credentialStores}/new`; - // Generate resource counter - getCredentialStoresCount = () => { - return this.server.schema.credentialStores.all().models.length; - }; - authenticateSession({}); - }); - - test('can create a new credential stores', async function (assert) { - assert.expect(1); - const count = getCredentialStoresCount(); - await visit(urls.newCredentialStore); - await fillIn('[name="name"]', 'random string'); - await click('[type="submit"]'); - assert.equal(getCredentialStoresCount(), count + 1); - }); - - test('can cancel create new credential stores', async function (assert) { - assert.expect(2); - const count = getCredentialStoresCount(); - await visit(urls.newCredentialStore); - await fillIn('[name="name"]', 'random string'); - await click('.rose-form-actions [type="button"]'); - assert.equal(currentURL(), urls.credentialStores); - assert.equal(getCredentialStoresCount(), count); - }); - - test('saving a new credential store with invalid fields displays error messages', async function (assert) { - assert.expect(2); - this.server.post('/credential-stores', () => { - 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.newCredentialStore); - await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'The request was invalid.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Name is required.' - ); - }); -});