diff --git a/addons/api/addon/models/credential.js b/addons/api/addon/models/credential.js index d7339b719..eb5474206 100644 --- a/addons/api/addon/models/credential.js +++ b/addons/api/addon/models/credential.js @@ -3,7 +3,7 @@ import GeneratedCredentialModel from '../generated/models/credential'; /** * Supported Credential types. */ -export const types = ['username_password', 'ssh_private_key']; +export const types = ['username_password', 'ssh_private_key', 'json']; export default class CredentialModel extends GeneratedCredentialModel { // =attributes diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 32f934c43..b2bfb2a95 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -455,6 +455,9 @@ credential: private_key_passphrase: label: Passphrase help: Optional. + json: + label: JSON + help: This is the JSON blob that provides credentials to the host. After this data is saved, you will not be able to edit it again without replacing it. scope: title: Scope title_plural: Scopes diff --git a/ui/admin/app/abilities/credential.js b/ui/admin/app/abilities/credential.js new file mode 100644 index 000000000..0faae0a97 --- /dev/null +++ b/ui/admin/app/abilities/credential.js @@ -0,0 +1,13 @@ +import CredentialAbility from 'api/abilities/credential'; +import { inject as service } from '@ember/service'; + +export default class OverrideCredentialAbility extends CredentialAbility { + // =service + @service features; + + get canRead() { + return (this.features.isEnabled('json-credentials') || this.model.type !== 'json') + ? super.canRead + : false; + } +} diff --git a/ui/admin/app/components/form/credential/index.hbs b/ui/admin/app/components/form/credential/index.hbs index a27830b5c..6d957e1b6 100644 --- a/ui/admin/app/components/form/credential/index.hbs +++ b/ui/admin/app/components/form/credential/index.hbs @@ -2,6 +2,7 @@ {{component (concat 'form/credential/' @model.type) model=@model + types=this.credentialTypes submit=@submit cancel=@cancel changeType=@changeType diff --git a/ui/admin/app/components/form/credential/index.js b/ui/admin/app/components/form/credential/index.js new file mode 100644 index 000000000..fa281a953 --- /dev/null +++ b/ui/admin/app/components/form/credential/index.js @@ -0,0 +1,19 @@ +import Component from '@glimmer/component'; +import { inject as service } from '@ember/service'; +import { types } from 'api/models/credential'; + +export default class FormCredentialComponent extends Component { + // =services + @service features; + + // =attributes + /** + * Returns an array of available credential types the user can create + * @type {object} + */ + get credentialTypes() { + return this.features.isEnabled('json-credentials') + ? types + : types.filter((type) => type !== 'json'); + } +} diff --git a/ui/admin/app/components/form/credential/json/index.hbs b/ui/admin/app/components/form/credential/json/index.hbs new file mode 100644 index 000000000..42f83c0a0 --- /dev/null +++ b/ui/admin/app/components/form/credential/json/index.hbs @@ -0,0 +1,112 @@ + + + + {{#if @model.errors.name}} + + {{#each @model.errors.name as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{#if @model.errors.description}} + + {{#each @model.errors.description as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + {{#if @model.isNew}} + + <:body> + + {{t 'form.type.label'}} + {{#each @types as |credentialType|}} + + {{t + (concat 'resources.credential.types.' credentialType) + }} + {{t + (concat 'resources.credential.help.' credentialType) + }} + + {{/each}} + + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{else}} + + {{t 'form.type.label'}} + {{t (concat 'resources.credential.types.' @model.type)}} + + + + + <:title>{{t 'resources.credential.form.json.label'}} + <:description>{{t 'resources.credential.form.json.help'}} + <:body> + + + + {{/if}} + + {{#if (can 'save model' @model)}} + + {{/if}} + + \ No newline at end of file diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs index c87c33efa..471893221 100644 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.hbs +++ b/ui/admin/app/components/form/credential/ssh_private_key/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -46,7 +42,7 @@ {{#if @model.isNew}} {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{#if @model.errors.username}} @@ -102,8 +96,6 @@ @label={{t 'resources.credential.form.private_key.label'}} @helperText={{t 'resources.credential.form.private_key.help'}} @error={{@model.errors.private_key}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.private_key}} @@ -121,8 +113,6 @@ @label={{t 'resources.credential.form.private_key_passphrase.label'}} @helperText={{t 'resources.credential.form.private_key_passphrase.help'}} @error={{@model.errors.private_key_passphrase}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/ssh_private_key/index.js b/ui/admin/app/components/form/credential/ssh_private_key/index.js deleted file mode 100644 index c2c5d5bcb..000000000 --- a/ui/admin/app/components/form/credential/ssh_private_key/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormSSHPrivateKeyCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/components/form/credential/username_password/index.hbs b/ui/admin/app/components/form/credential/username_password/index.hbs index 731f4deb9..538b998aa 100644 --- a/ui/admin/app/components/form/credential/username_password/index.hbs +++ b/ui/admin/app/components/form/credential/username_password/index.hbs @@ -12,8 +12,6 @@ @label={{t 'form.name.label'}} @error={{@model.errors.name}} @helperText={{t 'form.name.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.name}} @@ -31,8 +29,6 @@ @label={{t 'form.description.label'}} @error={{@model.errors.description}} @helperText={{t 'form.description.help'}} - disabled={{@model.isSaving}} - readonly={{false}} as |field| > {{#if @model.errors.description}} @@ -46,7 +42,7 @@ {{#if @model.isNew}} {{t 'form.type.label'}} - {{#each this.credentialTypes as |credentialType|}} + {{#each @types as |credentialType|}} {{#if @model.errors.username}} @@ -103,8 +97,6 @@ @label={{t 'resources.credential.form.password.label'}} @helperText={{t 'resources.credential.form.password.help'}} @error={{@model.errors.password}} - disabled={{@model.isSaving}} - readonly={{false}} autocomplete='new-password' as |field| > diff --git a/ui/admin/app/components/form/credential/username_password/index.js b/ui/admin/app/components/form/credential/username_password/index.js deleted file mode 100644 index 67d493c10..000000000 --- a/ui/admin/app/components/form/credential/username_password/index.js +++ /dev/null @@ -1,10 +0,0 @@ -import Component from '@glimmer/component'; -import { types } from 'api/models/credential'; - -export default class FormUserNamePasswordCredentialComponent extends Component { - // =attributes - /** - * @type {Array} - */ - credentialTypes = types; -} diff --git a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js index 28c7b2019..65e8c3577 100644 --- a/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js +++ b/ui/admin/app/routes/scopes/scope/credential-stores/credential-store/credentials/new.js @@ -29,15 +29,23 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialsNewRou 'scopes.scope.credential-stores.credential-store' ); let name, description; + /** + * this sets the credential attribute json_object to a string that + * replicates an empty json blob as CodeMirror expects a string + */ + const json_object = '{}'; + if (this.currentModel?.isNew) { ({ name, description } = this.currentModel); this.currentModel.rollbackAttributes(); } + return this.store.createRecord('credential', { type, credential_store_id, name, description, + json_object, }); } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 9f3fd2b00..b9adb26ff 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -508,21 +508,18 @@ border-radius: sizing.rems(xxs); height: var(--secret-editor-height); margin-bottom: sizing.rems(l); + width: 100%; .secret-editor-json { .CodeMirror { border-radius: sizing.rems(xxs); height: var(--secret-editor-height); + display: block; } .CodeMirror-gutters { background: var(--secret-editor-gutter); } - - // override CodeMirror padding to add padding to left of line numbers - .CodeMirror-linenumber { - padding: 0; - } } .secret-editor-skeleton-wrapper { diff --git a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs index e80fab03d..29a1c405c 100644 --- a/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs +++ b/ui/admin/app/templates/scopes/scope/targets/target/brokered-credential-sources.hbs @@ -37,7 +37,7 @@ }} - {{#if brokeredCredentialSource.isUnknown}} + {{#if (cannot 'read credential' brokeredCredentialSource)}} {{brokeredCredentialSource.displayName}} {{else}} {{#if brokeredCredentialSource.isStatic}} diff --git a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js index 298ee7c1e..ce7f2040d 100644 --- a/ui/admin/tests/acceptance/credential-store/credentials/create-test.js +++ b/ui/admin/tests/acceptance/credential-store/credentials/create-test.js @@ -1,5 +1,5 @@ import { module, test } from 'qunit'; -import { visit, currentURL, find, click, 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'; @@ -14,6 +14,8 @@ module( let getCredentialsCount; let getUsernamePasswordCredentialCount; let getUsernameKeyPairCredentialCount; + let getJsonCredentialCount; + let featuresService; const instances = { scopes: { @@ -33,6 +35,7 @@ module( hooks.beforeEach(function () { // Generate resources + featuresService = this.owner.lookup('service:features'); instances.scopes.org = this.server.create('scope', { type: 'org', scope: { id: 'global', type: 'global' }, @@ -50,7 +53,7 @@ module( urls.credentialStores = `${urls.projectScope}/credential-stores`; urls.staticCredentialStore = `${urls.credentialStores}/${instances.staticCredentialStore.id}`; urls.credentials = `${urls.staticCredentialStore}/credentials`; - urls.newCredential = `${urls.staticCredentialStore}/credentials/new`; + urls.newCredential = `${urls.credentials}/new`; // Generate resource counter getCredentialsCount = () => { return this.server.schema.credentials.all().models.length; @@ -64,17 +67,23 @@ module( return this.server.schema.credentials.where({ type: 'ssh_private_key' }) .length; }; + getJsonCredentialCount = () => { + return this.server.schema.credentials.where({ type: 'json' }).length; + }; authenticateSession({}); }); - test('Users can create a new username & password credential', async function (assert) { + test('users can create a new username & password credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernamePasswordCredentialCount = getUsernamePasswordCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernamePasswordCredentialCount(), @@ -82,15 +91,18 @@ module( ); }); - test('Users can create a new username & key pair credential', async function (assert) { + test('users can create a new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); const usernameKeyPairCredentialCount = getUsernameKeyPairCredentialCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); assert.strictEqual( getUsernameKeyPairCredentialCount(), @@ -98,42 +110,85 @@ module( ); }); - test('Users can cancel create new username & password credential', async function (assert) { + test('users can create a new json credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + const jsonCredentialCount = getJsonCredentialCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert.strictEqual(getCredentialsCount(), credentialsCount + 1); + assert.strictEqual(getJsonCredentialCount(), jsonCredentialCount + 1); + }); + + test('users can cancel create new username & password credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users can cancel create new username & key pair credential', async function (assert) { + test('users can cancel create new username & key pair credential', async function (assert) { assert.expect(2); const credentialsCount = getCredentialsCount(); - await visit(urls.newCredential); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); await fillIn('[name="name"]', 'random string'); await click('[value="ssh_private_key"]'); await click('.rose-form-actions [type="button"]'); + assert.strictEqual(currentURL(), urls.credentials); assert.strictEqual(getCredentialsCount(), credentialsCount); }); - test('Users cannot navigate to new credential route without proper authorization', async function (assert) { + test('users can cancel create new json credential', async function (assert) { + assert.expect(2); + const credentialsCount = getCredentialsCount(); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + await fillIn('[name="name"]', 'random string'); + await click('[value="json"]'); + await click('.rose-form-actions [type="button"]'); + + assert.strictEqual(currentURL(), urls.credentials); + assert.strictEqual(getCredentialsCount(), credentialsCount); + }); + + test('users cannot navigate to new credential route without proper authorization', async function (assert) { assert.expect(2); instances.staticCredentialStore.authorized_collection_actions.credentials = - []; - await visit(urls.staticCredentialStore); - assert.notOk( + instances.staticCredentialStore.authorized_collection_actions.credentials.filter( + (item) => { + item !== 'create'; + } + ); + await visit(urls.credentialStores); + + await click(`[href="${urls.staticCredentialStore}"]`); + + assert.false( instances.staticCredentialStore.authorized_collection_actions.credentials.includes( 'create' ) ); - assert.notOk(find(`[href="${urls.newCredential}"]`)); + assert.dom('.rose-layout-page-actions a').doesNotExist(); }); test('saving a new username & password credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -154,20 +209,21 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-password credential.' - ); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-password credential.'); }); test('saving a new username & key pair credential with invalid fields displays error messages', async function (assert) { assert.expect(2); + await visit(urls.credentials); this.server.post('/credentials', () => { return new Response( 400, @@ -188,17 +244,70 @@ module( } ); }); - await visit(urls.newCredential); + + await click(`[href="${urls.newCredential}"]`); await click('[value="ssh_private_key"]'); await click('[type="submit"]'); - assert.ok( - find('[role="alert"]').textContent.trim(), - 'Error in provided request.' - ); - assert.ok( - find('.rose-form-error-message').textContent.trim(), - 'Field required for creating a username-key-pair credential.' + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + assert + .dom('.rose-form-error-message') + .hasText('Field required for creating a username-key-pair credential.'); + }); + + test('saving a new json credential with invalid fields displays error messages', async function (assert) { + assert.expect(1); + await visit(urls.credentials); + this.server.post('/credentials', () => { + return new Response( + 400, + {}, + { + status: 400, + code: 'invalid_argument', + message: 'Error in provided request.', + details: { + request_fields: [ + { + name: 'attributes.json_object', + description: 'Field required for creating a json credential.', + }, + ], + }, + } + ); + }); + + await click(`[href="${urls.newCredential}"]`); + await click('[value="json"]'); + await click('[type="submit"]'); + + assert + .dom('.rose-notification-body') + .hasText('Error in provided request.'); + }); + + test('cannot navigate to json credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(4); + await visit(urls.credentials); + + await click(`[href="${urls.newCredential}"]`); + + assert.true( + instances.staticCredentialStore.authorized_collection_actions.credentials.includes( + 'create' + ) ); + assert.dom('.hds-form-radio-card').exists({ count: 2 }); + assert + .dom('.hds-form-radio-card:first-child input') + .hasAttribute('value', 'username_password'); + assert + .dom('.hds-form-radio-card:nth-child(2) input') + .hasAttribute('value', 'ssh_private_key'); }); } ); diff --git a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js index 84f4048a4..9f940eb04 100644 --- a/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js +++ b/ui/admin/tests/acceptance/targets/brokered-credential-sources-test.js @@ -15,6 +15,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { let credentialSourceCount; let randomlySelectedCredentialLibraries; let randomlySelectedCredentials; + let featuresService; const instances = { scopes: { @@ -37,11 +38,14 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { target: null, credentialLibraries: null, credentialLibrary: null, + credential: null, + jsonCredential: null, addBrokeredCredentialSources: null, brokeredCredentialSources: null, }; hooks.beforeEach(function () { + featuresService = this.owner.lookup('service:features'); // Generate resources instances.scopes.global = this.server.create('scope', { id: 'global' }); instances.scopes.org = this.server.create('scope', { @@ -60,7 +64,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { type: 'static', scope: instances.scopes.project, }); - instances.credentials = this.server.createList('credential', 2, { + instances.credentials = this.server.createList('credential', 3, { scope: instances.scopes.project, credentialStore: instances.staticCredentialStore, }); @@ -100,6 +104,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { urls.brokeredCredentialSources = `${urls.target}/brokered-credential-sources`; urls.credentialLibrary = `${urls.projectScope}/credential-stores/${instances.credentialLibrary.credentialStoreId}/credential-libraries/${instances.credentialLibrary.id}`; urls.credential = `${urls.projectScope}/credential-stores/${instances.credential.credentialStoreId}/credentials/${instances.credential.id}`; + urls.jsonCredential = `${urls.projectScope}/credential-stores/${instances.credentials[2].credentialStoreId}/credentials/${instances.credentials[2].id}`; urls.addBrokeredCredentialSources = `${urls.target}/add-brokered-credential-sources`; getCredentialLibraryCount = () => this.server.schema.credentialLibraries.all().models.length; @@ -137,6 +142,19 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { assert.strictEqual(currentURL(), urls.credential); }); + test('cannot navigate to a json type credential when feature is disabled', async function (assert) { + featuresService.disable('json-credentials'); + assert.expect(2); + const jsonCredential = instances.credentials[2]; + instances.target.update({ + brokeredCredentialSourceIds: [...randomlySelectedCredentials], + }); + await visit(urls.brokeredCredentialSources); + + assert.dom('.rose-table-row:nth-child(3)').includesText(jsonCredential.name); + assert.dom(`[href="${urls.jsonCredential}"]`).doesNotExist(); + }); + test('visiting add brokered credential sources', async function (assert) { assert.expect(1); await visit(urls.addBrokeredCredentialSources); @@ -157,7 +175,7 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { test('displays list of brokered credential sources with only credential libraries available', async function (assert) { assert.expect(2); instances.target.update({ - brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], + brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); await visit(urls.addBrokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, getCredentialLibraryCount()); @@ -300,12 +318,13 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentialLibraries], }); const credentialLibraryCount = getCredentialLibraryCount(); + const availableCredentialsCount = getCredentialCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialLibraryCount + 1); + assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); }); test('can remove a username & password type credential', async function (assert) { @@ -314,12 +333,13 @@ module('Acceptance | targets | brokered credential sources', function (hooks) { brokeredCredentialSourceIds: [...randomlySelectedCredentials], }); const credentialCount = getCredentialCount(); + const availableCredentialsCount = getCredentialLibraryCount(); await visit(urls.brokeredCredentialSources); assert.strictEqual(findAll('tbody tr').length, credentialCount); await click('tbody tr .rose-dropdown-button-danger'); assert.strictEqual(findAll('tbody tr').length, credentialCount - 1); await visit(urls.addBrokeredCredentialSources); - assert.strictEqual(findAll('tbody tr').length, credentialCount + 1); + assert.strictEqual(findAll('tbody tr').length, availableCredentialsCount + 1); }); test('cannot remove credential libraries without proper authorization', async function (assert) { diff --git a/ui/admin/tests/unit/abilities/credential-test.js b/ui/admin/tests/unit/abilities/credential-test.js new file mode 100644 index 000000000..4e76ace86 --- /dev/null +++ b/ui/admin/tests/unit/abilities/credential-test.js @@ -0,0 +1,90 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Abilities | credential', function (hooks) { + setupTest(hooks); + + test('canRead', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const credential = { + authorized_actions: ['read'], + type: 'json' + }; + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.ok( + service.can('read credential', credential) + ); + }); + + test('cannot read without proper authorized actions', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const credential = { + authorized_actions: [], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.notOk( + service.can('read credential', credential) + ); + }); + + test('cannot read without proper authorized actions and json-credentials disabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + const credential = { + authorized_actions: [], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.notOk( + service.can('read credential', credential) + ); + }); + + test('cannot read when json-credentials is disabled', function (assert) { + assert.expect(3); + const service = this.owner.lookup('service:can'); + const featuresService = this.owner.lookup('service:features'); + featuresService.disable('json-credentials'); + const credential = { + authorized_actions: ['read'], + type: 'json' + }; + assert.notOk( + service.can('read credential', credential) + ); + credential.type = 'username_password' + assert.ok( + service.can('read credential', credential) + ); + credential.type = 'ssh_private_key' + assert.ok( + service.can('read credential', credential) + ); + }); +}); diff --git a/yarn.lock b/yarn.lock index 23144f89f..08691421b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8668,7 +8668,7 @@ babel-types@^6.26.0: babel6-plugin-strip-class-callcheck@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/babel6-plugin-strip-class-callcheck/-/babel6-plugin-strip-class-callcheck-6.0.0.tgz#de841c1abebbd39f78de0affb2c9a52ee228fddf" - integrity sha1-3oQcGr6705943gr/ssmlLuIo/d8= + integrity sha512-biNFJ7JAK4+9BwswDGL0dmYpvXHvswOFR/iKg3Q/f+pNxPEa5bWZkLHI1fW4spPytkHGMe7f/XtYyhzml9hiWg== babylon@^6.18.0: version "6.18.0" @@ -12446,7 +12446,7 @@ ember-cli-terser@^4.0.2: ember-cli-test-info@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/ember-cli-test-info/-/ember-cli-test-info-1.0.0.tgz#ed4e960f249e97523cf891e4aed2072ce84577b4" - integrity sha1-7U6WDySel1I8+JHkrtIHLOhFd7Q= + integrity sha512-dEVTIpmUfCzweC97NGf6p7L6XKBwV2GmSM4elmzKvkttEp5P7AvGA9uGyN4GqFq+RwhW+2b0I2qlX00w+skm+A== dependencies: ember-cli-string-utils "^1.0.0" @@ -20506,7 +20506,7 @@ npm-conf@^1.1.3: npm-git-info@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/npm-git-info/-/npm-git-info-1.0.3.tgz#a933c42ec321e80d3646e0d6e844afe94630e1d5" - integrity sha1-qTPELsMh6A02RuDW6ESv6UYw4dU= + integrity sha512-i5WBdj4F/ULl16z9ZhsJDMl1EQCMQhHZzBwNnKL2LOA+T8IHNeRkLCVz9uVV9SzUdGTbDq+1oXhIYMe+8148vw== npm-install-checks@^3.0.2: version "3.0.2"