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"