From f097eab53a03d51e555732fa8170e3eb15ba9526 Mon Sep 17 00:00:00 2001 From: dharini Date: Wed, 1 Sep 2021 19:38:48 -0700 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20cred=20library=20cap?= =?UTF-8?q?abilities=20list/create?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../api/mirage/factories/credential-store.js | 5 + .../credential-store/credential-libraries.js | 13 +- .../credential-libraries/index.js | 10 +- .../credential-store/-actions.hbs | 15 ++- .../credential-store/-navigation.hbs | 13 +- .../credential-libraries/index.hbs | 23 +++- .../scopes/scope/credential-stores/index.hbs | 15 ++- .../create-test.js} | 16 ++- .../credential-library/list-test.js | 95 +++++++++++++++ .../acceptance/credential-stores-test.js | 112 ------------------ 10 files changed, 185 insertions(+), 132 deletions(-) rename ui/admin/tests/acceptance/{credential-libraries-test.js => credential-library/create-test.js} (89%) create mode 100644 ui/admin/tests/acceptance/credential-library/list-test.js delete mode 100644 ui/admin/tests/acceptance/credential-stores-test.js 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.' - ); - }); -}); From e0d3b25be2f9aa1c9f3b0dc20a5386791ffdd231 Mon Sep 17 00:00:00 2001 From: dharini Date: Thu, 2 Sep 2021 10:55:51 -0700 Subject: [PATCH 2/7] =?UTF-8?q?chore:=20=F0=9F=A4=96=20merge=20main=20+=20?= =?UTF-8?q?resolve=20conflicts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acceptance/credential-stores-test.js | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 ui/admin/tests/acceptance/credential-stores-test.js diff --git a/ui/admin/tests/acceptance/credential-stores-test.js b/ui/admin/tests/acceptance/credential-stores-test.js new file mode 100644 index 000000000..54764b5a3 --- /dev/null +++ b/ui/admin/tests/acceptance/credential-stores-test.js @@ -0,0 +1,112 @@ +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.' + ); + }); +}); From 594f951ca7371a3f1b969ac42d9a4cbcfc5ce666 Mon Sep 17 00:00:00 2001 From: dharini Date: Thu, 2 Sep 2021 12:16:45 -0700 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=F0=9F=90=9B=20scope=20route=20for?= =?UTF-8?q?=20create=20collection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../credential-libraries/index.js | 4 +++- .../credential-libraries/index.hbs | 18 +++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) 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 ef7e6e66b..bfc92aa63 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 @@ -4,7 +4,9 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari // =methods setupController(controller) { - const scope = this.modelFor('scopes.scope'); + const scope = this.modelFor( + 'scopes.scope.credential-stores.credential-store' + ); super.setupController(...arguments); controller.setProperties({ scope }); } 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 ac3448ddd..d263c8a10 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 @@ -57,7 +57,7 @@ (can 'list collection' this.scope collection='credential-libraries') }} {{! can list (at least): show default welcome message}} - {{t 'resources.credential-library.messages.none.description'}} + {{t 'resources.credential-library.description'}} {{else if (can 'create collection' this.scope collection='credential-libraries' @@ -76,12 +76,16 @@ {{/if}} - - - {{t 'titles.new'}} - + {{#if + (can 'create collection' this.scope collection='credential-libraries') + }} + + + {{t 'titles.new'}} + + {{/if}} From d8aca33fcd56da88d58f3ff7ba62bb7c3e2c919c Mon Sep 17 00:00:00 2001 From: dharini Date: Thu, 2 Sep 2021 14:10:54 -0700 Subject: [PATCH 4/7] =?UTF-8?q?fix:=20=F0=9F=90=9B=20update=20const=20name?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../credential-store/credential-libraries.js | 6 +++--- .../credential-libraries/index.js | 4 ++-- .../credential-libraries/index.hbs | 16 +++++++++++++--- 3 files changed, 18 insertions(+), 8 deletions(-) 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 a58c7cc52..06748f330 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 @@ -14,12 +14,12 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari * @return {Promise{[CredentialLibraryModel]}} */ model() { - const scope = this.modelFor( + const credentialStore = this.modelFor( 'scopes.scope.credential-stores.credential-store' ); - const { id: credential_store_id } = scope; + const { id: credential_store_id } = credentialStore; if ( - this.can.can('list collection', scope, { + this.can.can('list collection', credentialStore, { collection: 'credential-libraries', }) ) { 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 bfc92aa63..0b08bfbc7 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 @@ -4,10 +4,10 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari // =methods setupController(controller) { - const scope = this.modelFor( + const credentialStore = this.modelFor( 'scopes.scope.credential-stores.credential-store' ); super.setupController(...arguments); - controller.setProperties({ scope }); + controller.setProperties({ credentialStore }); } } 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 d263c8a10..f4c87f82b 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 @@ -54,13 +54,19 @@ > {{#if - (can 'list collection' this.scope collection='credential-libraries') + (can + 'list collection' + this.credentialStore + collection='credential-libraries' + ) }} {{! can list (at least): show default welcome message}} {{t 'resources.credential-library.description'}} {{else if (can - 'create collection' this.scope collection='credential-libraries' + 'create collection' + this.credentialStore + collection='credential-libraries' )}} {{! can create (only): show create-but-not-list welcome message}} {{t @@ -77,7 +83,11 @@ {{#if - (can 'create collection' this.scope collection='credential-libraries') + (can + 'create collection' + this.credentialStore + collection='credential-libraries' + ) }} Date: Fri, 3 Sep 2021 10:22:54 -0700 Subject: [PATCH 5/7] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20accounts=20capabilit?= =?UTF-8?q?y?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- addons/api/mirage/factories/auth-method.js | 5 + .../auth-methods/auth-method/accounts.js | 15 ++- .../auth-method/accounts/account/index.js | 11 ++- .../auth-method/accounts/index.js | 9 ++ .../auth-methods/auth-method/-actions.hbs | 16 ++-- .../auth-methods/auth-method/-navigation.hbs | 9 +- .../auth-method/accounts/index.hbs | 36 +++++++- .../create-test.js} | 37 +++++++- .../tests/acceptance/accounts/list-test.js | 92 +++++++++++++++++++ 9 files changed, 208 insertions(+), 22 deletions(-) create mode 100644 ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/index.js rename ui/admin/tests/acceptance/{accounts-test.js => accounts/create-test.js} (76%) create mode 100644 ui/admin/tests/acceptance/accounts/list-test.js diff --git a/addons/api/mirage/factories/auth-method.js b/addons/api/mirage/factories/auth-method.js index 9e14a9e99..2ce4b4053 100644 --- a/addons/api/mirage/factories/auth-method.js +++ b/addons/api/mirage/factories/auth-method.js @@ -12,6 +12,11 @@ export default factory.extend({ 'update', 'delete', ], + authorized_collection_actions: () => { + return { + accounts: ['create', 'list'], + }; + }, id: (i) => `auth-method-id-${i}`, // Cycle through available types diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts.js index 4e177662a..d4e1ab55a 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts.js @@ -10,7 +10,7 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsRoute extends Route @service intl; @service notify; - + @service can; // =methods /** @@ -18,10 +18,15 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsRoute extends Route * @return {Promise{[AccountModel]}} */ model() { - const { id: auth_method_id } = this.modelFor( - 'scopes.scope.auth-methods.auth-method' - ); - return this.store.query('account', { auth_method_id }); + const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method'); + const { id: auth_method_id } = authMethod; + if ( + this.can.can('list collection', authMethod, { + collection: 'accounts', + }) + ) { + return this.store.query('account', { auth_method_id }); + } } // =actions diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/index.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/index.js index a3d16587d..20a6940d7 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/index.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/account/index.js @@ -1,3 +1,10 @@ import Route from '@ember/routing/route'; - -export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountIndexRoute extends Route {} +export default class ScopesScopeAuthMethodsAuthMethodAccountsAccountIndexRoute extends Route { + // setupController(controller) { + // const authMethod = this.modelFor( + // 'scopes.scope.auth-methods.auth-method' + // ); + // super.setupController(...arguments); + // controller.setProperties({ authMethod }); + // } +} diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/index.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/index.js new file mode 100644 index 000000000..069b6e434 --- /dev/null +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method/accounts/index.js @@ -0,0 +1,9 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeAuthMethodsAuthMethodAccountsIndexRoute extends Route { + setupController(controller) { + const authMethod = this.modelFor('scopes.scope.auth-methods.auth-method'); + super.setupController(...arguments); + controller.setProperties({ authMethod }); + } +} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs index e1e15ecf3..66d339d35 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs @@ -55,12 +55,16 @@ {{/if}} {{/if}} - - {{t 'resources.account.actions.create'}} - + {{#if (can 'create collection' @model collection='accounts')}} + + + {{t 'resources.account.actions.create'}} + + {{/if}} + {{#if (can 'delete model' @model)}} {{t 'titles.details'}} - - {{t 'resources.account.title_plural'}} - + {{#if (can 'navigate collection' @model collection='accounts')}} + + + {{t 'resources.account.title_plural'}} + + {{/if}} \ No newline at end of file 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 2006e7f52..d320365ae 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 @@ -112,12 +112,38 @@ as |message| > - {{t 'resources.account.messages.welcome.description'}} + + {{#if (can 'list collection' this.authMethod collection='accounts')}} + {{! can list (at least): show default welcome message}} + {{t 'resources.account.description'}} + + {{else if (can + 'create collection' this.authMethod collection='accounts' + )}} + {{! can create (only): show create-but-not-list welcome message}} + {{t + 'descriptions.create-but-not-list' + resource=(t 'resources.account.title_plural') + }} + {{else}} + {{! can neither list nor create }} + {{t + 'descriptions.neither-list-nor-create' + resource=(t 'resources.account.title_plural') + }} + {{/if}} + - - - {{t 'resources.account.actions.create'}} - + + {{#if (can 'create collection' this.authMethod collection='accounts')}} + + + + {{t 'resources.account.actions.create'}} + + {{/if}} diff --git a/ui/admin/tests/acceptance/accounts-test.js b/ui/admin/tests/acceptance/accounts/create-test.js similarity index 76% rename from ui/admin/tests/acceptance/accounts-test.js rename to ui/admin/tests/acceptance/accounts/create-test.js index 5430e36db..e68e69695 100644 --- a/ui/admin/tests/acceptance/accounts-test.js +++ b/ui/admin/tests/acceptance/accounts/create-test.js @@ -11,7 +11,7 @@ import { //invalidateSession, } from 'ember-simple-auth/test-support'; -module('Acceptance | accounts', function (hooks) { +module('Acceptance | accounts | create', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); @@ -80,6 +80,41 @@ module('Acceptance | accounts', function (hooks) { assert.equal(this.server.db.accounts.length, accountsCount + 1); }); + test('Users cannot create a new account without proper authorization', async function (assert) { + assert.expect(2); + instances.authMethod.authorized_collection_actions.accounts = []; + await visit(urls.authMethod); + assert.notOk( + instances.authMethod.authorized_collection_actions.accounts.includes( + 'create' + ) + ); + assert.notOk(find(`.rose-layout-page-actions [href="${urls.newAccount}"]`)); + }); + + test('Users can navigate to new account route with proper authorization', async function (assert) { + assert.expect(2); + await visit(urls.authMethod); + assert.ok( + instances.authMethod.authorized_collection_actions.accounts.includes( + 'create' + ) + ); + assert.ok(find(`[href="${urls.accounts}"]`)); + }); + + test('Users cannot navigate to new account route without proper authorization', async function (assert) { + assert.expect(2); + instances.authMethod.authorized_collection_actions.accounts = []; + await visit(urls.authMethod); + assert.notOk( + instances.authMethod.authorized_collection_actions.accounts.includes( + 'create' + ) + ); + assert.notOk(find(`[href="${urls.accounts}"]`)); + }); + test('can cancel a new account creation', async function (assert) { assert.expect(2); const accountsCount = this.server.db.accounts.length; diff --git a/ui/admin/tests/acceptance/accounts/list-test.js b/ui/admin/tests/acceptance/accounts/list-test.js new file mode 100644 index 000000000..755e4585e --- /dev/null +++ b/ui/admin/tests/acceptance/accounts/list-test.js @@ -0,0 +1,92 @@ +import { module, test } from 'qunit'; +import { visit, click, 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 | accounts | list', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + authMethods: null, + account: null, + }; + const urls = { + orgScope: null, + authMethods: null, + accounts: null, + newAccount: null, + account: null, + }; + + hooks.beforeEach(function () { + authenticateSession({}); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.authMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + }); + instances.account = this.server.create('account', { + 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.accounts = `${urls.authMethod}/accounts`; + urls.newAccount = `${urls.accounts}/new`; + urls.account = `${urls.accounts}/${instances.account.id}`; + }); + + hooks.afterEach(async function () { + const notification = find('.rose-notification'); + if (notification) { + await click('.rose-notification-dismiss'); + } + }); + + test('Users can navigate to accounts with proper authorization', async function (assert) { + assert.expect(2); + await visit(urls.authMethod); + assert.ok( + instances.authMethod.authorized_collection_actions.accounts.includes( + 'list' + ) + ); + assert.ok(find(`[href="${urls.accounts}"]`)); + }); + + test('User cannot navigate to index without either list or create actions', async function (assert) { + assert.expect(2); + instances.authMethod.authorized_collection_actions.accounts = []; + await visit(urls.authMethod); + assert.notOk( + instances.authMethod.authorized_collection_actions.accounts.includes( + 'list' + ) + ); + assert.notOk(find(`[href="${urls.accounts}"]`)); + }); + + test('User can navigate to index with only create action', async function (assert) { + assert.expect(2); + instances.authMethod.authorized_collection_actions.accounts = ['create']; + await visit(urls.authMethod); + assert.ok(find(`[href="${urls.accounts}"]`)); + assert.ok(find(`.rose-layout-page-actions [href="${urls.newAccount}"]`)); + }); +}); From 14190fdf67a3293c1bdbfd2842b6c3bc66bc0a29 Mon Sep 17 00:00:00 2001 From: Dharini Jeeva Date: Fri, 3 Sep 2021 11:09:33 -0700 Subject: [PATCH 6/7] Update list-test.js --- ui/admin/tests/acceptance/credential-library/list-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/admin/tests/acceptance/credential-library/list-test.js b/ui/admin/tests/acceptance/credential-library/list-test.js index 5ed0289cd..753b157ba 100644 --- a/ui/admin/tests/acceptance/credential-library/list-test.js +++ b/ui/admin/tests/acceptance/credential-library/list-test.js @@ -59,7 +59,7 @@ module('Acceptance | credential-libraries | list', function (hooks) { authenticateSession({}); }); - test('Users can navigate to users with proper authorization', async function (assert) { + test('Users can navigate to credential libraries with proper authorization', async function (assert) { assert.expect(2); await visit(urls.credentialStore); assert.ok( From 547330821fc7ddba173117b37922cfd72c60f25a Mon Sep 17 00:00:00 2001 From: Randall Morey Date: Fri, 3 Sep 2021 14:51:33 -0400 Subject: [PATCH 7/7] =?UTF-8?q?chore:=20=F0=9F=A4=96=20enable=20capabiliti?= =?UTF-8?q?es=20feature?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ui/admin/config/environment.js | 2 +- ui/desktop/config/environment.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index 5e363c78a..3b1a8df4b 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -94,7 +94,7 @@ module.exports = function (environment) { 'oidc-account-crud': true, search: false, filter: false, - capabilities: false, + capabilities: true, 'credential-store': true, }, }; diff --git a/ui/desktop/config/environment.js b/ui/desktop/config/environment.js index 986e50b0a..9c9f67b6e 100644 --- a/ui/desktop/config/environment.js +++ b/ui/desktop/config/environment.js @@ -58,7 +58,7 @@ module.exports = function (environment) { oidc: true, search: false, filter: false, - capabilities: false, + capabilities: true, 'credential-store': false, }, };