Merge Boundary UI OSS branch 'main' at commit f0530e1f
This commit is contained in:
commit
0c1543e13c
34 files changed
+1421
-182
No files matched your search
@@ -2,11 +2,11 @@ import ModelAbility from './model';
|
||||
/**
|
||||
* Provides abilities for credential.
|
||||
*/
|
||||
export default class CredentialLibaryAbility extends ModelAbility {
|
||||
export default class CredentialLibraryAbility extends ModelAbility {
|
||||
// =permissions
|
||||
|
||||
/**
|
||||
* Only "known" credential libary types may be read.
|
||||
* Only "known" credential library types may be read.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get canRead() {
|
||||
|
||||
@@ -67,4 +67,50 @@ export default class GeneratedCredentialLibraryModel extends BaseModel {
|
||||
description: 'The path in Vault to request credentials from.',
|
||||
})
|
||||
path;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: 'The username to use when making an SSH connection.',
|
||||
})
|
||||
username;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: 'The desired key type to use when generating a private key.',
|
||||
})
|
||||
key_type;
|
||||
|
||||
@attr('number', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Specifies the number of bits used when generating the private key. Not used if key type is ed25519',
|
||||
})
|
||||
key_bits;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description: 'Specifies the requested time to live for the certificate.',
|
||||
})
|
||||
ttl;
|
||||
|
||||
@attr('string', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Specifies the key id that the created certificate should have.',
|
||||
})
|
||||
key_id;
|
||||
|
||||
@attr('object-as-array', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Specifies a map of the critical options that the certificate should be signed for.',
|
||||
})
|
||||
critical_options;
|
||||
|
||||
@attr('object-as-array', {
|
||||
isNestedAttribute: true,
|
||||
description:
|
||||
'Specifies a map of the extensions that the certificate should be signed for.',
|
||||
})
|
||||
extensions;
|
||||
}
|
||||
@@ -5,12 +5,20 @@ import GeneratedCredentialLibraryModel from '../generated/models/credential-libr
|
||||
*/
|
||||
export const options = {
|
||||
http_method: ['GET', 'POST'],
|
||||
key_type: ['ed25519', 'ecdsa', 'rsa'],
|
||||
};
|
||||
|
||||
export const TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC = 'vault-generic';
|
||||
export const TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE =
|
||||
'vault-ssh-certificate';
|
||||
|
||||
/**
|
||||
* Supported Credential Library types.
|
||||
*/
|
||||
export const types = ['vault'];
|
||||
export const TYPES_CREDENTIAL_LIBRARY = Object.freeze([
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
]);
|
||||
|
||||
export default class CredentialLibraryModel extends GeneratedCredentialLibraryModel {
|
||||
// =attributes
|
||||
@@ -20,7 +28,7 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isUnknown() {
|
||||
return !types.includes(this.type);
|
||||
return !TYPES_CREDENTIAL_LIBRARY.includes(this.type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -28,6 +36,25 @@ export default class CredentialLibraryModel extends GeneratedCredentialLibraryMo
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isVault() {
|
||||
return this.type === 'vault';
|
||||
return (
|
||||
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC ||
|
||||
this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if credential is a generic vault type.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isVaultGeneric() {
|
||||
return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
}
|
||||
|
||||
/**
|
||||
* True if credential is a vault ssh cert type.
|
||||
* @type {boolean}
|
||||
*/
|
||||
get isVaultSSHCertificate() {
|
||||
return this.type === TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import ApplicationSerializer from './application';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from '../models/credential-library';
|
||||
|
||||
export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
||||
// =properties
|
||||
@@ -16,6 +17,10 @@ export default class CredentialLibrarySerializer extends ApplicationSerializer {
|
||||
serialize() {
|
||||
const serialized = super.serialize(...arguments);
|
||||
if (serialized.attributes) {
|
||||
if (serialized.type !== TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC) {
|
||||
delete serialized.attributes.http_method;
|
||||
}
|
||||
|
||||
// Serialize `http_request_body` only if `http_method` is POST
|
||||
if (!serialized.attributes?.http_method?.match(/post/i))
|
||||
delete serialized.attributes.http_request_body;
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import Transform from '@ember-data/serializer/transform';
|
||||
|
||||
export default class ObjectAsArrayTransform extends Transform {
|
||||
deserialize(serialized) {
|
||||
const obj = serialized || {};
|
||||
return Object.entries(obj).map(([key, value]) => ({ key, value }));
|
||||
}
|
||||
serialize(deserialized) {
|
||||
const array = deserialized || [];
|
||||
|
||||
if (!array.length) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array.reduce((result, currentValue) => {
|
||||
const { key, value } = currentValue;
|
||||
result[key] = value;
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/transforms/object-as-array';
|
||||
@@ -2,12 +2,15 @@ import factory from '../generated/factories/credential-library';
|
||||
import { faker } from '@faker-js/faker';
|
||||
import permissions from '../helpers/permissions';
|
||||
import generateId from '../helpers/id';
|
||||
|
||||
const types = ['vault'];
|
||||
import {
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
TYPES_CREDENTIAL_LIBRARY,
|
||||
} from 'api/models/credential-library';
|
||||
|
||||
export default factory.extend({
|
||||
id: () => generateId('cl_'),
|
||||
type: (i) => types[i % types.length],
|
||||
type: (i) => TYPES_CREDENTIAL_LIBRARY[i % TYPES_CREDENTIAL_LIBRARY.length],
|
||||
|
||||
authorized_actions: () =>
|
||||
permissions.authorizedActionsFor('credential-library') || [
|
||||
@@ -19,12 +22,19 @@ export default factory.extend({
|
||||
|
||||
attributes() {
|
||||
switch (this.type) {
|
||||
case 'vault':
|
||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC:
|
||||
return {
|
||||
http_method: 'GET',
|
||||
http_request_body: faker.random.word(),
|
||||
path: faker.system.directoryPath(),
|
||||
};
|
||||
case TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE:
|
||||
return {
|
||||
username: faker.random.word(),
|
||||
key_type: faker.random.word(),
|
||||
key_bits: faker.datatype.number(999),
|
||||
path: faker.system.directoryPath(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,6 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library';
|
||||
|
||||
module('Unit | Model | credential library', function (hooks) {
|
||||
setupTest(hooks);
|
||||
@@ -13,7 +14,9 @@ module('Unit | Model | credential library', function (hooks) {
|
||||
test('it has isUnknown property and returns the expected values', async function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const modelA = store.createRecord('credential-library', { type: 'vault' });
|
||||
const modelA = store.createRecord('credential-library', {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
});
|
||||
const modelB = store.createRecord('credential-library', {
|
||||
type: 'unknown',
|
||||
});
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import {
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
} from 'api/models/credential-library';
|
||||
|
||||
module('Unit | Serializer | credential library', function (hooks) {
|
||||
setupTest(hooks);
|
||||
@@ -9,7 +13,7 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-library');
|
||||
const record = store.createRecord('credential-library', {
|
||||
type: 'vault',
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
path: '/vault/path',
|
||||
@@ -19,7 +23,7 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
@@ -27,6 +31,13 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
critical_options: null,
|
||||
extensions: null,
|
||||
key_bits: null,
|
||||
key_id: null,
|
||||
key_type: null,
|
||||
ttl: null,
|
||||
username: null,
|
||||
},
|
||||
});
|
||||
});
|
||||
@@ -40,7 +51,7 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
id: '1',
|
||||
type: 'credential-library',
|
||||
attributes: {
|
||||
type: 'vault',
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
path: '/vault/path',
|
||||
@@ -53,13 +64,20 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'vault',
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
http_method: 'GET',
|
||||
critical_options: null,
|
||||
extensions: null,
|
||||
key_bits: null,
|
||||
key_id: null,
|
||||
key_type: null,
|
||||
ttl: null,
|
||||
username: null,
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
@@ -75,8 +93,14 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
let serializedRecord = record.serialize();
|
||||
assert.deepEqual(serializedRecord, {
|
||||
attributes: {
|
||||
http_method: null,
|
||||
path: null,
|
||||
critical_options: null,
|
||||
extensions: null,
|
||||
key_bits: null,
|
||||
key_id: null,
|
||||
key_type: null,
|
||||
ttl: null,
|
||||
username: null,
|
||||
},
|
||||
credential_store_id: null,
|
||||
description: null,
|
||||
@@ -89,6 +113,7 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const record = store.createRecord('credential-library', {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
http_method: 'GET',
|
||||
http_request_body: 'body',
|
||||
});
|
||||
@@ -99,11 +124,18 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
attributes: {
|
||||
http_method: 'GET',
|
||||
path: null,
|
||||
critical_options: null,
|
||||
extensions: null,
|
||||
key_bits: null,
|
||||
key_id: null,
|
||||
key_type: null,
|
||||
ttl: null,
|
||||
username: null,
|
||||
},
|
||||
credential_store_id: null,
|
||||
description: null,
|
||||
name: null,
|
||||
type: null,
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
},
|
||||
'http_request_body attribute is not expected'
|
||||
);
|
||||
@@ -117,13 +149,66 @@ module('Unit | Serializer | credential library', function (hooks) {
|
||||
http_method: 'POST',
|
||||
http_request_body: 'body',
|
||||
path: null,
|
||||
critical_options: null,
|
||||
extensions: null,
|
||||
key_bits: null,
|
||||
key_id: null,
|
||||
key_type: null,
|
||||
ttl: null,
|
||||
username: null,
|
||||
},
|
||||
credential_store_id: null,
|
||||
description: null,
|
||||
name: null,
|
||||
type: null,
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
},
|
||||
'http_request_body attribute is expected'
|
||||
);
|
||||
});
|
||||
|
||||
test('it serializes vault_ssh_certificate correctly', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('credential-library');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'credential-library',
|
||||
attributes: {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
version: 1,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
path: '/vault/path',
|
||||
username: 'user',
|
||||
key_type: 'rsa',
|
||||
key_bits: 100,
|
||||
ttl: '100',
|
||||
key_id: 'id',
|
||||
extensions: [{ key: 'key', value: 'value' }],
|
||||
critical_options: [{ key: 'key', value: 'value' }],
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('credential-library', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
credential_store_id: null,
|
||||
name: 'Name',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
path: '/vault/path',
|
||||
username: 'user',
|
||||
key_type: 'rsa',
|
||||
key_bits: 100,
|
||||
ttl: '100',
|
||||
key_id: 'id',
|
||||
extensions: { key: 'value' },
|
||||
critical_options: { key: 'value' },
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,52 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Transform | object as array', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it deserializes an object to an array of key value objects', function (assert) {
|
||||
assert.expect(1);
|
||||
let transform = this.owner.lookup('transform:object-as-array');
|
||||
const deserialized = transform.deserialize({
|
||||
first: 'Hey',
|
||||
second: 'There',
|
||||
third: 'Everyone',
|
||||
});
|
||||
assert.deepEqual(deserialized, [
|
||||
{ key: 'first', value: 'Hey' },
|
||||
{ key: 'second', value: 'There' },
|
||||
{ key: 'third', value: 'Everyone' },
|
||||
]);
|
||||
});
|
||||
|
||||
test('it deserializes undefined to an empty object', function (assert) {
|
||||
assert.expect(1);
|
||||
let transform = this.owner.lookup('transform:object-as-array');
|
||||
const deserialized = transform.deserialize(undefined);
|
||||
assert.deepEqual(deserialized, []);
|
||||
});
|
||||
|
||||
test('it serializes an array of key value objects to an object', function (assert) {
|
||||
assert.expect(1);
|
||||
let transform = this.owner.lookup('transform:object-as-array');
|
||||
const serialized = transform.serialize([
|
||||
{ key: 'first', value: 'Hey' },
|
||||
{ key: 'second', value: 'There' },
|
||||
{ key: 'third', value: 'Everyone' },
|
||||
]);
|
||||
|
||||
assert.deepEqual(serialized, {
|
||||
first: 'Hey',
|
||||
second: 'There',
|
||||
third: 'Everyone',
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes an empty array of key value objects to null', function (assert) {
|
||||
assert.expect(1);
|
||||
let transform = this.owner.lookup('transform:object-as-array');
|
||||
const serialized = transform.serialize([]);
|
||||
|
||||
assert.deepEqual(serialized, null);
|
||||
});
|
||||
});
|
||||
@@ -34,6 +34,7 @@ titles:
|
||||
provider: Provider
|
||||
success: Success
|
||||
create-resources: Create Resources
|
||||
choose-an-option: Choose an option
|
||||
descriptions:
|
||||
empty-set: There are no items to display yet. You may be able to add items or try back later.
|
||||
cluster-url-initialization: To get started, please enter your cluster URL
|
||||
|
||||
@@ -468,9 +468,33 @@ credential-library:
|
||||
http_request_body:
|
||||
label: HTTP Method POST Request Body
|
||||
help: The body of the HTTP request the library sends to Vault when requesting credentials. Only allowed when HTTP method is set to POST.
|
||||
username:
|
||||
label: Username
|
||||
help: The username to use when making an SSH connection. This can be templated to use the requesting user's name. This will be included in the valid_principles when making the request to Vault.
|
||||
key_type:
|
||||
label: Key Type
|
||||
help: Specifies the desired key type to use when generating a private key.
|
||||
key_bits:
|
||||
label: Key Bits
|
||||
help: Specifies the number of bits used generating the private key.
|
||||
ttl:
|
||||
label: TTL
|
||||
help: Specifies the Requested Time to Live for the certificate.
|
||||
key_id:
|
||||
label: Key ID
|
||||
help: Specifies the key id that the created certificate should have.
|
||||
critical_options:
|
||||
label: Critical Options
|
||||
help: Specifies a map of the critical options that the certificate should be signed for.
|
||||
extensions:
|
||||
label: Extensions
|
||||
help: Specifies a map of the extensions that the certificate should be signed for.
|
||||
types:
|
||||
vault: Vault
|
||||
static: Static
|
||||
vault-generic: Generic Secrets
|
||||
vault-ssh-certificate: SSH Certificates
|
||||
help:
|
||||
vault-generic: Authorize sessions using credentials brokered by Vault
|
||||
vault-ssh-certificate: Authorize sessions using the SSH Secrets Engine in Vault
|
||||
credential:
|
||||
title: Credential
|
||||
title_plural: Credentials
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@embroider/test-setup": "^0.48.1",
|
||||
"@hashicorp/design-system-components": "^1.3.2",
|
||||
"@hashicorp/design-system-components": "^1.5.1",
|
||||
"codemirror": "5.65.7",
|
||||
"ember-auto-import": "^2.4.2",
|
||||
"ember-cli-babel": "^7.26.10",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import CredentialLibraryAbility from 'api/abilities/credential-library';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default class OverrideCredentialLibraryAbility extends CredentialLibraryAbility {
|
||||
// =service
|
||||
@service features;
|
||||
|
||||
/**
|
||||
* This override ensures that vault ssh cert may be read only if the
|
||||
* credential-library-vault-ssh-certificate feature flag is enabled.
|
||||
* All other types are subject to the standard logic found in the api addon.
|
||||
*/
|
||||
get canRead() {
|
||||
return this.features.isEnabled(
|
||||
'credential-library-vault-ssh-certificate'
|
||||
) || !this.model.isVaultSSHCertificate
|
||||
? super.canRead
|
||||
: false;
|
||||
}
|
||||
}
|
||||
@@ -1,129 +1,10 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
<form.input
|
||||
@name='vault_path'
|
||||
@type='text'
|
||||
@value={{@model.path}}
|
||||
@label={{t 'resources.credential-library.form.vault_path.label'}}
|
||||
@error={{@model.errors.path}}
|
||||
@helperText={{t 'resources.credential-library.form.vault_path.help'}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.path}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.path as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.fieldset>
|
||||
<:title>{{t 'resources.credential-library.form.http_method.label'}}</:title>
|
||||
<:description>
|
||||
{{t 'resources.credential-library.form.http_method.help'}}
|
||||
</:description>
|
||||
<:body>
|
||||
<form.radioGroup
|
||||
@name='http_method'
|
||||
@selectedValue={{@model.http_method}}
|
||||
@changed={{fn (mut @model.http_method)}}
|
||||
@inline={{true}}
|
||||
disabled={{@model.isSaving}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each this.httpMethodOptions as |httpMethod|}}
|
||||
<radioGroup.radio @label={{httpMethod}} @value={{httpMethod}} />
|
||||
{{/each}}
|
||||
</form.radioGroup>
|
||||
</:body>
|
||||
</form.fieldset>
|
||||
|
||||
{{#if this.isHttpRequestBodyAllowed}}
|
||||
<form.textarea
|
||||
@name='http_request_body'
|
||||
@type='text'
|
||||
@value={{@model.http_request_body}}
|
||||
@label={{t 'resources.credential-library.form.http_request_body.label'}}
|
||||
@error={{@model.errors.http_request_body}}
|
||||
@helperText={{t
|
||||
'resources.credential-library.form.http_request_body.help'
|
||||
}}
|
||||
@disabled={{this.cannotSaveHttpRequestBody}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.http_request_body}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.http_request_body as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
</Rose::Form>
|
||||
{{#if @model.type}}
|
||||
{{component
|
||||
(concat 'form/credential-library/' @model.type)
|
||||
model=@model
|
||||
submit=@submit
|
||||
cancel=@cancel
|
||||
changeType=@changeType
|
||||
edit=@edit
|
||||
}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,169 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{false}}
|
||||
@isOptional={{true}}
|
||||
@value={{@model.name}}
|
||||
@isInvalid={{@model.errors.name}}
|
||||
@type='text'
|
||||
name='name'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
||||
{{#if @model.errors.name}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Textarea::Field
|
||||
@isRequired={{false}}
|
||||
@isOptional={{true}}
|
||||
@value={{@model.description}}
|
||||
@isInvalid={{@model.errors.description}}
|
||||
name='description'
|
||||
disabled={{form.disabled}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.description.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
|
||||
{{#if @model.errors.description}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
||||
{{#if
|
||||
(and @model.isNew (feature-flag 'credential-library-vault-ssh-certificate'))
|
||||
}}
|
||||
<Hds::Form::RadioCard::Group @name='type' @alignment='center' as |G|>
|
||||
<G.Legend>{{t 'form.type.label'}}</G.Legend>
|
||||
{{#each this.types as |type|}}
|
||||
<G.RadioCard
|
||||
@value={{type}}
|
||||
@maxWidth='20rem'
|
||||
@checked={{eq type @model.type}}
|
||||
{{on 'input' (fn @changeType type)}}
|
||||
as |R|
|
||||
>
|
||||
<R.Label>{{t
|
||||
(concat 'resources.credential-library.types.' type)
|
||||
}}</R.Label>
|
||||
<R.Description>{{t
|
||||
(concat 'resources.credential-library.help.' type)
|
||||
}}</R.Description>
|
||||
</G.RadioCard>
|
||||
{{/each}}
|
||||
</Hds::Form::RadioCard::Group>
|
||||
{{else}}
|
||||
<InfoField
|
||||
@value={{t (concat 'resources.credential-library.types.' @model.type)}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.type.label'}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t (concat 'resources.credential-library.help.' @model.type)}}
|
||||
</F.HelperText>
|
||||
</InfoField>
|
||||
{{/if}}
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@isOptional={{false}}
|
||||
@value={{@model.path}}
|
||||
@isInvalid={{@model.errors.path}}
|
||||
@type='text'
|
||||
name='vault_path'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'path')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.credential-library.form.vault_path.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.vault_path.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.path}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.path as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Radio::Group
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'http_method')}}
|
||||
@isRequired={{true}}
|
||||
@layout='horizontal'
|
||||
@name='http_method'
|
||||
as |G|
|
||||
>
|
||||
<G.Legend>
|
||||
{{t 'resources.credential-library.form.http_method.label'}}
|
||||
</G.Legend>
|
||||
<G.HelperText>
|
||||
{{t 'resources.credential-library.form.http_method.help'}}
|
||||
</G.HelperText>
|
||||
{{#each this.httpMethodOptions as |httpMethod|}}
|
||||
<G.Radio::Field
|
||||
checked={{eq @model.http_method httpMethod}}
|
||||
@value={{httpMethod}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{httpMethod}}</F.Label>
|
||||
</G.Radio::Field>
|
||||
{{/each}}
|
||||
</Hds::Form::Radio::Group>
|
||||
|
||||
{{#if this.isHttpRequestBodyAllowed}}
|
||||
<Hds::Form::Textarea::Field
|
||||
@isRequired={{true}}
|
||||
@isOptional={{false}}
|
||||
@value={{@model.http_request_body}}
|
||||
@isInvalid={{@model.errors.http_request_body}}
|
||||
name='http_request_body'
|
||||
disabled={{form.disabled}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.credential-library.form.http_request_body.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.http_request_body.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.http_request_body}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.http_request_body as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Textarea::Field>
|
||||
{{/if}}
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
</Rose::Form>
|
||||
+11
-4
@@ -1,19 +1,26 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { options } from 'api/models/credential-library';
|
||||
import { computed } from '@ember/object';
|
||||
import {
|
||||
options,
|
||||
TYPES_CREDENTIAL_LIBRARY,
|
||||
} from 'api/models/credential-library';
|
||||
|
||||
export default class FormCredentialLibraryIndexComponent extends Component {
|
||||
export default class FormCredentialLibraryVaultGenericComponent extends Component {
|
||||
// =properties
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
httpMethodOptions = options.http_method;
|
||||
|
||||
/**
|
||||
*
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
types = TYPES_CREDENTIAL_LIBRARY;
|
||||
|
||||
/**
|
||||
* Only allow HTTP request body field if http_method is set to POST.
|
||||
* @type {boolean}
|
||||
*/
|
||||
@computed('args.model.http_method')
|
||||
get isHttpRequestBodyAllowed() {
|
||||
return this.args.model.http_method?.match(/post/i);
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
<Rose::Form
|
||||
@edit={{@edit}}
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.name}}
|
||||
@isInvalid={{@model.errors.name}}
|
||||
@type='text'
|
||||
name='name'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'name')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.name.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.name.help'}}</F.HelperText>
|
||||
{{#if @model.errors.name}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Textarea::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.description}}
|
||||
@isInvalid={{@model.errors.description}}
|
||||
name='description'
|
||||
disabled={{form.disabled}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.description.label'}}</F.Label>
|
||||
<F.HelperText>{{t 'form.description.help'}}</F.HelperText>
|
||||
{{#if @model.errors.description}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Textarea::Field>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<Hds::Form::RadioCard::Group @name='type' @alignment='center' as |G|>
|
||||
<G.Legend>{{t 'form.type.label'}}</G.Legend>
|
||||
{{#each this.types as |type|}}
|
||||
<G.RadioCard
|
||||
@value={{type}}
|
||||
@maxWidth='20rem'
|
||||
@checked={{eq type @model.type}}
|
||||
{{on 'input' (fn @changeType type)}}
|
||||
as |R|
|
||||
>
|
||||
<R.Label>{{t
|
||||
(concat 'resources.credential-library.types.' type)
|
||||
}}</R.Label>
|
||||
<R.Description>{{t
|
||||
(concat 'resources.credential-library.help.' type)
|
||||
}}</R.Description>
|
||||
</G.RadioCard>
|
||||
{{/each}}
|
||||
</Hds::Form::RadioCard::Group>
|
||||
{{else}}
|
||||
<InfoField
|
||||
@value={{t (concat 'resources.credential-library.types.' @model.type)}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'form.type.label'}}</F.Label>
|
||||
<F.HelperText>
|
||||
{{t (concat 'resources.credential-library.help.' @model.type)}}
|
||||
</F.HelperText>
|
||||
</InfoField>
|
||||
{{/if}}
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.path}}
|
||||
@isInvalid={{@model.errors.path}}
|
||||
@type='text'
|
||||
name='vault_path'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'path')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.credential-library.form.vault_path.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.vault_path.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.path}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.path as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isRequired={{true}}
|
||||
@value={{@model.username}}
|
||||
@isInvalid={{@model.errors.username}}
|
||||
@type='text'
|
||||
name='username'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'username')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.credential-library.form.username.label'}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.username.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.username}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.username as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::Select::Field
|
||||
@isOptional={{true}}
|
||||
@width='100%'
|
||||
name='key_type'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'key_type')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.credential-library.form.key_type.label'}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.key_type.help'
|
||||
}}</F.HelperText>
|
||||
<F.Options>
|
||||
<option disabled hidden selected value=''>{{t
|
||||
'titles.choose-an-option'
|
||||
}}</option>
|
||||
{{#each this.keyTypes as |keyType|}}
|
||||
<option
|
||||
value={{keyType}}
|
||||
selected={{eq keyType @model.key_type}}
|
||||
>{{keyType}}</option>
|
||||
{{/each}}
|
||||
</F.Options>
|
||||
{{#if @model.errors.key_type}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.key_type as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::Select::Field>
|
||||
|
||||
{{#if this.showKeyBits}}
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.key_bits}}
|
||||
@isInvalid={{@model.errors.key_bits}}
|
||||
@type='text'
|
||||
name='key_bits'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'key_bits')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t
|
||||
'resources.credential-library.form.key_bits.label'
|
||||
}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.key_bits.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.key_bits}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.key_bits as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
{{/if}}
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.ttl}}
|
||||
@isInvalid={{@model.errors.ttl}}
|
||||
@type='text'
|
||||
name='ttl'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'ttl')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.credential-library.form.ttl.label'}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.ttl.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.ttl}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.ttl as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Hds::Form::TextInput::Field
|
||||
@isOptional={{true}}
|
||||
@value={{@model.key_id}}
|
||||
@isInvalid={{@model.errors.key_id}}
|
||||
@type='text'
|
||||
name='key_id'
|
||||
disabled={{form.disabled}}
|
||||
{{on 'input' (set-from-event @model 'key_id')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label>{{t 'resources.credential-library.form.key_id.label'}}</F.Label>
|
||||
<F.HelperText>{{t
|
||||
'resources.credential-library.form.key_id.help'
|
||||
}}</F.HelperText>
|
||||
{{#if @model.errors.key_id}}
|
||||
<F.Error as |E|>
|
||||
{{#each @model.errors.key_id as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</F.Error>
|
||||
{{/if}}
|
||||
</Hds::Form::TextInput::Field>
|
||||
|
||||
<Form::Field::KeyValueList
|
||||
@layout='horizontal'
|
||||
@isOptional={{true}}
|
||||
@name='critical_options'
|
||||
@options={{@model.critical_options}}
|
||||
@addOption={{fn this.addOption 'critical_options'}}
|
||||
@removeOptionByIndex={{fn this.removeOptionByIndex 'critical_options'}}
|
||||
@disabled={{form.disabled}}
|
||||
as |M|
|
||||
>
|
||||
<M.Legend>
|
||||
{{t 'resources.credential-library.form.critical_options.label'}}
|
||||
</M.Legend>
|
||||
<M.HelperText>
|
||||
{{t 'resources.credential-library.form.critical_options.help'}}
|
||||
</M.HelperText>
|
||||
{{#if @model.errors.critical_options}}
|
||||
<M.Error as |E|>
|
||||
{{#each @model.errors.critical_options as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</M.Error>
|
||||
{{/if}}
|
||||
</Form::Field::KeyValueList>
|
||||
|
||||
<Form::Field::KeyValueList
|
||||
@layout='horizontal'
|
||||
@isOptional={{true}}
|
||||
@name='extensions'
|
||||
@options={{@model.extensions}}
|
||||
@addOption={{fn this.addOption 'extensions'}}
|
||||
@removeOptionByIndex={{fn this.removeOptionByIndex 'extensions'}}
|
||||
@disabled={{form.disabled}}
|
||||
as |M|
|
||||
>
|
||||
<M.Legend>
|
||||
{{t 'resources.credential-library.form.extensions.label'}}
|
||||
</M.Legend>
|
||||
<M.HelperText>
|
||||
{{t 'resources.credential-library.form.extensions.help'}}
|
||||
</M.HelperText>
|
||||
{{#if @model.errors.extensions}}
|
||||
<M.Error as |E|>
|
||||
{{#each @model.errors.extensions as |error|}}
|
||||
<E.Message>{{error.message}}</E.Message>
|
||||
{{/each}}
|
||||
</M.Error>
|
||||
{{/if}}
|
||||
</Form::Field::KeyValueList>
|
||||
|
||||
{{#if (can 'save model' @model)}}
|
||||
<form.actions
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
</Rose::Form>
|
||||
@@ -0,0 +1,55 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { action, set } from '@ember/object';
|
||||
import {
|
||||
options,
|
||||
TYPES_CREDENTIAL_LIBRARY,
|
||||
} from 'api/models/credential-library';
|
||||
|
||||
export default class FormCredentialLibraryVaultSshCertComponent extends Component {
|
||||
// =properties
|
||||
|
||||
/**
|
||||
* @type {object}
|
||||
*/
|
||||
keyTypes = options.key_type;
|
||||
|
||||
/**
|
||||
* @type {Array.<string>}
|
||||
*/
|
||||
types = TYPES_CREDENTIAL_LIBRARY;
|
||||
|
||||
/**
|
||||
* Boolean to determine if the key bits field should be displayed on the form
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get showKeyBits() {
|
||||
const keyType = this.args.model.key_type;
|
||||
return keyType === 'rsa' || keyType === 'ecdsa';
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a key/value option object. We recreate a new array after adding
|
||||
* so that ember is aware that the array has been modified.
|
||||
* @param field {string}
|
||||
* @param key {string}
|
||||
* @param value {string}
|
||||
*/
|
||||
@action
|
||||
addOption(field, { key, value }) {
|
||||
const existingArray = this.args.model[field] ?? [];
|
||||
const newArray = [...existingArray, { key, value }];
|
||||
set(this.args.model, field, newArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an option by index. We recreate a new array after
|
||||
* splicing out the item so that ember is aware that the array has been modified.
|
||||
* @param field {string}
|
||||
* @param index {number}
|
||||
*/
|
||||
@action
|
||||
removeOptionByIndex(field, index) {
|
||||
const newArray = this.args.model[field].filter((_, i) => i !== index);
|
||||
set(this.args.model, field, newArray);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<Hds::Form::Fieldset
|
||||
@layout={{@layout}}
|
||||
@isRequired={{@isRequired}}
|
||||
@isOptional={{@isOptional}}
|
||||
as |F|
|
||||
>
|
||||
{{yield F}}
|
||||
<F.Control>
|
||||
<Hds::Table class='key-value-list' name={{@name}}>
|
||||
<:head as |H|>
|
||||
<H.Tr>
|
||||
<H.Th>{{t 'form.key.label'}}</H.Th>
|
||||
<H.Th>{{t 'form.value.label'}}</H.Th>
|
||||
<H.Th>
|
||||
{{#if @removeOptionByIndex}}
|
||||
{{t 'titles.actions'}}
|
||||
{{/if}}
|
||||
</H.Th>
|
||||
</H.Tr>
|
||||
</:head>
|
||||
<:body as |B|>
|
||||
|
||||
{{#each @options as |option index|}}
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{option.key}}
|
||||
@type='text'
|
||||
disabled={{@disabled}}
|
||||
{{on 'input' (set-from-event option 'key')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label class='sr-only'>{{t 'form.key.label'}}</F.Label>
|
||||
</Hds::Form::TextInput::Field>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{option.value}}
|
||||
@type='text'
|
||||
disabled={{@disabled}}
|
||||
{{on 'input' (set-from-event option 'value')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label class='sr-only'>{{t 'form.value.label'}}</F.Label>
|
||||
</Hds::Form::TextInput::Field>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
{{#if @removeOptionByIndex}}
|
||||
<Hds::Button
|
||||
@text={{t 'actions.remove'}}
|
||||
@color='critical'
|
||||
@icon='trash'
|
||||
@isIconOnly={{true}}
|
||||
disabled={{@disabled}}
|
||||
{{on 'click' (fn @removeOptionByIndex index)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
{{/each}}
|
||||
|
||||
<B.Tr>
|
||||
<B.Td>
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{this.newOptionKey}}
|
||||
@type='text'
|
||||
disabled={{@disabled}}
|
||||
{{on 'input' (set-from-event this 'newOptionKey')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label class='sr-only'>{{t 'form.key.label'}}</F.Label>
|
||||
</Hds::Form::TextInput::Field></B.Td>
|
||||
<B.Td>
|
||||
<Hds::Form::TextInput::Field
|
||||
@value={{this.newOptionValue}}
|
||||
@type='text'
|
||||
disabled={{@disabled}}
|
||||
{{on 'input' (set-from-event this 'newOptionValue')}}
|
||||
as |F|
|
||||
>
|
||||
<F.Label class='sr-only'>{{t 'form.value.label'}}</F.Label>
|
||||
</Hds::Form::TextInput::Field>
|
||||
</B.Td>
|
||||
<B.Td>
|
||||
<Hds::Button
|
||||
@text={{t 'actions.add'}}
|
||||
@color='secondary'
|
||||
type='button'
|
||||
disabled={{or @disabled (not this.newOptionKey)}}
|
||||
{{on 'click' this.addOption}}
|
||||
/>
|
||||
</B.Td>
|
||||
</B.Tr>
|
||||
|
||||
</:body>
|
||||
</Hds::Table>
|
||||
</F.Control>
|
||||
</Hds::Form::Fieldset>
|
||||
@@ -0,0 +1,42 @@
|
||||
import Component from '@glimmer/component';
|
||||
import { tracked } from '@glimmer/tracking';
|
||||
import { action } from '@ember/object';
|
||||
import { assert } from '@ember/debug';
|
||||
|
||||
export default class MappingListComponent extends Component {
|
||||
// =attributes
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked newOptionKey = '';
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
@tracked newOptionValue = '';
|
||||
|
||||
// =actions
|
||||
|
||||
/**
|
||||
* If a new key value is entered and an addOption method was specified,
|
||||
* calls addOption with the new key and value. Resets key and value.
|
||||
*/
|
||||
@action
|
||||
addOption() {
|
||||
assert(
|
||||
'[boundary-admin-key-value-list-field] `@addOption` is required.',
|
||||
this.args.addOption
|
||||
);
|
||||
|
||||
if (this.newOptionKey) {
|
||||
this.args.addOption({
|
||||
key: this.newOptionKey,
|
||||
value: this.newOptionValue,
|
||||
});
|
||||
}
|
||||
|
||||
this.newOptionKey = '';
|
||||
this.newOptionValue = '';
|
||||
}
|
||||
}
|
||||
+1
@@ -7,6 +7,7 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
@service intl;
|
||||
|
||||
// =attributes
|
||||
queryParams = ['type'];
|
||||
|
||||
/**
|
||||
* Translated new credential libraries breadcrumb
|
||||
|
||||
+18
@@ -1,5 +1,6 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { action } from '@ember/object';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesCredentialLibraryRoute extends Route {
|
||||
// =services
|
||||
@@ -19,4 +20,21 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
reload: true,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of array fields in order to force the instance
|
||||
* into a dirty state. This ensures that `model.rollbackAttributes()` reverts
|
||||
* to the original expected array.
|
||||
*
|
||||
* The deep copy implemented here is required to ensure that both the
|
||||
* array itself and its members are all new.
|
||||
*
|
||||
* @param {credentialLibraryModel} credentialLibrary
|
||||
*/
|
||||
@action
|
||||
edit(credentialLibrary) {
|
||||
const { critical_options, extensions } = credentialLibrary;
|
||||
credentialLibrary.critical_options = structuredClone(critical_options);
|
||||
credentialLibrary.extensions = structuredClone(extensions);
|
||||
}
|
||||
}
|
||||
+30
-2
@@ -1,10 +1,22 @@
|
||||
import Route from '@ember/routing/route';
|
||||
import { action } from '@ember/object';
|
||||
import { inject as service } from '@ember/service';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC } from 'api/models/credential-library';
|
||||
|
||||
export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrariesNewRoute extends Route {
|
||||
// =services
|
||||
|
||||
@service store;
|
||||
@service router;
|
||||
@service features;
|
||||
|
||||
// =attributes
|
||||
|
||||
queryParams = {
|
||||
type: {
|
||||
refreshModel: true,
|
||||
},
|
||||
};
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -12,13 +24,29 @@ export default class ScopesScopeCredentialStoresCredentialStoreCredentialLibrari
|
||||
* Creates a new unsaved credential library in current credential store.
|
||||
* @return {CredentialLibraryModel}
|
||||
*/
|
||||
model() {
|
||||
const { id: credential_store_id, type } = this.modelFor(
|
||||
model({ type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC }) {
|
||||
const { id: credential_store_id } = this.modelFor(
|
||||
'scopes.scope.credential-stores.credential-store'
|
||||
);
|
||||
|
||||
// Set the type to generic vault if feature flag isn't enabled in cases where
|
||||
// user sets the query parameter manually
|
||||
if (!this.features.isEnabled('credential-library-vault-ssh-certificate')) {
|
||||
type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
}
|
||||
|
||||
return this.store.createRecord('credential-library', {
|
||||
type,
|
||||
credential_store_id,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update type of credential library
|
||||
* @param {string} type
|
||||
*/
|
||||
@action
|
||||
changeType(type) {
|
||||
this.router.replaceWith({ queryParams: { type } });
|
||||
}
|
||||
}
|
||||
@@ -637,3 +637,47 @@
|
||||
margin-top: sizing.rems(xs) - sizing.rems(xxxxs); // 10
|
||||
}
|
||||
}
|
||||
|
||||
.key-value-list {
|
||||
border: 0;
|
||||
|
||||
/* stylelint-disable selector-class-pattern */
|
||||
.hds-table__thead {
|
||||
.hds-table__th {
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.hds-table__thead,
|
||||
.hds-table__tbody {
|
||||
.hds-table__tr {
|
||||
// Remove background color on all cells (especially header cells)
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.hds-table__th,
|
||||
.hds-table__td {
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
// Rely on right padding for cell spacing...
|
||||
padding-left: 0;
|
||||
|
||||
&:last-child {
|
||||
// ...except in the last cell, which doesn't need right padding
|
||||
padding-right: 0;
|
||||
|
||||
// Shrink last cell to minimum possible width
|
||||
width: 1%;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
// Reset field margins to rely instead on cell paddings
|
||||
.hds-form-field--layout-vertical,
|
||||
.hds-form-text-input {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
/* stylelint-enable selector-class-pattern */
|
||||
}
|
||||
+1
@@ -50,6 +50,7 @@
|
||||
<page.body>
|
||||
<Form::CredentialLibrary
|
||||
@model={{@model}}
|
||||
@edit={{route-action 'edit' @model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
/>
|
||||
|
||||
+1
@@ -19,6 +19,7 @@
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
@changeType={{route-action 'changeType'}}
|
||||
/>
|
||||
</page.body>
|
||||
</Rose::Layout::Page>
|
||||
@@ -15,7 +15,7 @@ const featureEditions = {
|
||||
'vault-worker-filter': false,
|
||||
'target-worker-filters-v2': true,
|
||||
'target-network-address': true,
|
||||
'credential-library-vault-ssh-cert': false,
|
||||
'credential-library-vault-ssh-certificate': false,
|
||||
},
|
||||
};
|
||||
featureEditions.enterprise = {
|
||||
@@ -23,6 +23,7 @@ featureEditions.enterprise = {
|
||||
'ssh-target': true,
|
||||
'vault-worker-filter': true,
|
||||
'target-worker-filters-v2-ingress': true,
|
||||
'credential-library-vault-ssh-certificate': true,
|
||||
};
|
||||
featureEditions.hcp = {
|
||||
...featureEditions.enterprise,
|
||||
@@ -152,7 +153,7 @@ module.exports = function (environment) {
|
||||
ENV.featureFlags['target-worker-filters-v2-ingress'] = true;
|
||||
ENV.featureFlags['target-worker-filters-v2-hcp'] = true;
|
||||
ENV.featureFlags['target-network-address'] = true;
|
||||
ENV.featureFlags['credential-library-vault-ssh-cert'] = true;
|
||||
ENV.featureFlags['credential-library-vault-ssh-certificate'] = true;
|
||||
}
|
||||
|
||||
if (environment === 'test') {
|
||||
@@ -178,7 +179,7 @@ module.exports = function (environment) {
|
||||
ENV.featureFlags['vault-worker-filter'] = true;
|
||||
ENV.featureFlags['target-worker-filters-v2'] = true;
|
||||
ENV.featureFlags['target-network-address'] = true;
|
||||
ENV.featureFlags['credential-library-vault-ssh-cert'] = true;
|
||||
ENV.featureFlags['credential-library-vault-ssh-certificate'] = true;
|
||||
}
|
||||
|
||||
if (environment === 'production') {
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, click, fillIn, currentURL, find } from '@ember/test-helpers';
|
||||
import {
|
||||
visit,
|
||||
click,
|
||||
fillIn,
|
||||
currentURL,
|
||||
find,
|
||||
select,
|
||||
} from '@ember/test-helpers';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import { Response } from 'miragejs';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library';
|
||||
|
||||
module('Acceptance | credential-libraries | create', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -83,6 +91,76 @@ module('Acceptance | credential-libraries | create', function (hooks) {
|
||||
assert.strictEqual(getCredentialLibraryCount(), count + 1);
|
||||
});
|
||||
|
||||
test('can create a new credential library of type vault ssh cert', async function (assert) {
|
||||
assert.expect(12);
|
||||
const count = getCredentialLibraryCount();
|
||||
await visit(urls.newCredentialLibrary);
|
||||
await click('[value="vault-ssh-certificate"]');
|
||||
await fillIn('[name="name"]', 'name');
|
||||
await fillIn('[name="description"]', 'description');
|
||||
await fillIn('[name="vault_path"]', 'path');
|
||||
await fillIn('[name="username"]', 'username');
|
||||
await select('[name="key_type"]', 'rsa');
|
||||
await fillIn('[name="key_bits"]', 100);
|
||||
await fillIn('[name="ttl"]', 'ttl');
|
||||
await fillIn('[name="key_id"]', 'key_id');
|
||||
await fillIn(
|
||||
'[name="critical_options"] tbody td:nth-of-type(1) input',
|
||||
'co_key'
|
||||
);
|
||||
await fillIn(
|
||||
'[name="critical_options"] tbody td:nth-of-type(2) input',
|
||||
'co_value'
|
||||
);
|
||||
await click('[name="critical_options"] button');
|
||||
await fillIn(
|
||||
'[name="extensions"] tbody td:nth-of-type(1) input',
|
||||
'ext_key'
|
||||
);
|
||||
await fillIn(
|
||||
'[name="extensions"] tbody td:nth-of-type(2) input',
|
||||
'ext_value'
|
||||
);
|
||||
await click('[name="extensions"] button');
|
||||
await click('[type="submit"]');
|
||||
assert.strictEqual(getCredentialLibraryCount(), count + 1);
|
||||
assert.strictEqual(
|
||||
this.server.schema.credentialLibraries.where({
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
}).length,
|
||||
1
|
||||
);
|
||||
const credentialLibrary = this.server.schema.credentialLibraries.findBy({
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.strictEqual(credentialLibrary.name, 'name');
|
||||
assert.strictEqual(credentialLibrary.description, 'description');
|
||||
assert.strictEqual(credentialLibrary.attributes.path, 'path');
|
||||
assert.strictEqual(credentialLibrary.attributes.username, 'username');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_type, 'rsa');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_bits, 100);
|
||||
assert.strictEqual(credentialLibrary.attributes.ttl, 'ttl');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_id, 'key_id');
|
||||
assert.deepEqual(credentialLibrary.attributes.critical_options, {
|
||||
co_key: 'co_value',
|
||||
});
|
||||
assert.deepEqual(credentialLibrary.attributes.extensions, {
|
||||
ext_key: 'ext_value',
|
||||
});
|
||||
});
|
||||
|
||||
test('ecdsa and rsa key types bring up a key bits field', async function (assert) {
|
||||
assert.expect(3);
|
||||
await visit(urls.newCredentialLibrary);
|
||||
await click('[value="vault-ssh-certificate"]');
|
||||
await select('[name="key_type"]', 'ed25519');
|
||||
assert.dom('[name="key_bits"]').doesNotExist();
|
||||
await select('[name="key_type"]', 'ecdsa');
|
||||
assert.dom('[name="key_bits"]').isVisible();
|
||||
await select('[name="key_type"]', 'rsa');
|
||||
assert.dom('[name="key_bits"]').isVisible();
|
||||
});
|
||||
|
||||
test('Users cannot navigate to new credential library route without proper authorization', async function (assert) {
|
||||
assert.expect(2);
|
||||
instances.credentialStore.authorized_collection_actions[
|
||||
@@ -107,7 +185,7 @@ module('Acceptance | credential-libraries | create', function (hooks) {
|
||||
assert.strictEqual(getCredentialLibraryCount(), count);
|
||||
});
|
||||
|
||||
test('saving a new credential library with invalid fields displays error messasges', async function (assert) {
|
||||
test('saving a new credential library with invalid fields displays error messages', async function (assert) {
|
||||
assert.expect(2);
|
||||
this.server.post('/credential-libraries', () => {
|
||||
return new Response(
|
||||
@@ -135,8 +213,17 @@ module('Acceptance | credential-libraries | create', function (hooks) {
|
||||
'The request was invalid.'
|
||||
);
|
||||
assert.ok(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
find('.hds-form-error__message').textContent.trim(),
|
||||
'Name is required.'
|
||||
);
|
||||
});
|
||||
|
||||
test('cannot select vault ssh cert when feature is disabled', async function (assert) {
|
||||
const featuresService = this.owner.lookup('service:features');
|
||||
featuresService.disable('credential-library-vault-ssh-certificate');
|
||||
assert.expect(1);
|
||||
await visit(urls.newCredentialLibrary);
|
||||
|
||||
assert.dom('[value="vault-ssh-certificate"]').doesNotExist();
|
||||
});
|
||||
});
|
||||
@@ -4,6 +4,7 @@ import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import a11yAudit from 'ember-a11y-testing/test-support/audit';
|
||||
import { authenticateSession } from 'ember-simple-auth/test-support';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library';
|
||||
|
||||
module('Acceptance | credential-libraries | read', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -78,6 +79,23 @@ module('Acceptance | credential-libraries | read', function (hooks) {
|
||||
assert.notOk(find('main tbody .rose-table-header-cell a'));
|
||||
});
|
||||
|
||||
test('cannot navigate to vault ssh cert form when feature is not enabled', async function (assert) {
|
||||
assert.expect(1);
|
||||
instances.credentialLibrary = this.server.create('credential-library', {
|
||||
scope: instances.scopes.project,
|
||||
credentialStore: instances.credentialStore,
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
await visit(
|
||||
`${urls.credentialLibraries}/${instances.credentialLibrary.id}`
|
||||
);
|
||||
const featuresService = this.owner.lookup('service:features');
|
||||
featuresService.disable('credential-library-vault-ssh-certificate');
|
||||
await visit(urls.credentialLibraries);
|
||||
|
||||
assert.dom('.rose-table-body tr:nth-of-type(2) a').doesNotExist();
|
||||
});
|
||||
|
||||
test('visiting an unknown credential library displays 404 message', async function (assert) {
|
||||
assert.expect(1);
|
||||
await visit(urls.unknownCredentialLibrary);
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { visit, click, fillIn, currentURL, find } from '@ember/test-helpers';
|
||||
import {
|
||||
visit,
|
||||
click,
|
||||
fillIn,
|
||||
currentURL,
|
||||
find,
|
||||
select,
|
||||
} 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';
|
||||
import { TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE } from 'api/models/credential-library';
|
||||
|
||||
module('Acceptance | credential-libraries | update', function (hooks) {
|
||||
setupApplicationTest(hooks);
|
||||
@@ -130,7 +138,7 @@ module('Acceptance | credential-libraries | update', function (hooks) {
|
||||
'The request was invalid.'
|
||||
);
|
||||
assert.ok(
|
||||
find('.rose-form-error-message').textContent.trim(),
|
||||
find('.hds-form-error__message').textContent.trim(),
|
||||
'Name is required.'
|
||||
);
|
||||
});
|
||||
@@ -181,4 +189,61 @@ module('Acceptance | credential-libraries | update', function (hooks) {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('can update a vault ssh cert credential library and save changes', async function (assert) {
|
||||
assert.expect(10);
|
||||
instances.credentialLibrary = this.server.create('credential-library', {
|
||||
scope: instances.scopes.project,
|
||||
credentialStore: instances.credentialStore,
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
await visit(
|
||||
`${urls.credentialLibraries}/${instances.credentialLibrary.id}`
|
||||
);
|
||||
await click('form [type="button"]:not(:disabled)', 'Activate edit mode');
|
||||
await fillIn('[name="name"]', 'name');
|
||||
await fillIn('[name="description"]', 'description');
|
||||
await fillIn('[name="vault_path"]', 'path');
|
||||
await fillIn('[name="username"]', 'username');
|
||||
await select('[name="key_type"]', 'rsa');
|
||||
await fillIn('[name="key_bits"]', 100);
|
||||
await fillIn('[name="ttl"]', 'ttl');
|
||||
await fillIn('[name="key_id"]', 'key_id');
|
||||
await fillIn(
|
||||
'[name="critical_options"] tbody td:nth-of-type(1) input',
|
||||
'co_key'
|
||||
);
|
||||
await fillIn(
|
||||
'[name="critical_options"] tbody td:nth-of-type(2) input',
|
||||
'co_value'
|
||||
);
|
||||
await click('[name="critical_options"] button');
|
||||
await fillIn(
|
||||
'[name="extensions"] tbody td:nth-of-type(1) input',
|
||||
'ext_key'
|
||||
);
|
||||
await fillIn(
|
||||
'[name="extensions"] tbody td:nth-of-type(2) input',
|
||||
'ext_value'
|
||||
);
|
||||
await click('[name="extensions"] button');
|
||||
await click('[type="submit"]');
|
||||
const credentialLibrary = this.server.schema.credentialLibraries.findBy({
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.strictEqual(credentialLibrary.name, 'name');
|
||||
assert.strictEqual(credentialLibrary.description, 'description');
|
||||
assert.strictEqual(credentialLibrary.attributes.path, 'path');
|
||||
assert.strictEqual(credentialLibrary.attributes.username, 'username');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_type, 'rsa');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_bits, 100);
|
||||
assert.strictEqual(credentialLibrary.attributes.ttl, 'ttl');
|
||||
assert.strictEqual(credentialLibrary.attributes.key_id, 'key_id');
|
||||
assert.deepEqual(credentialLibrary.attributes.critical_options, {
|
||||
co_key: 'co_value',
|
||||
});
|
||||
assert.deepEqual(credentialLibrary.attributes.extensions, {
|
||||
ext_key: 'ext_value',
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
import { hbs } from 'ember-cli-htmlbars';
|
||||
import { setupIntl } from 'ember-intl/test-support';
|
||||
|
||||
module('Integration | Component | key-value-list', function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
setupIntl(hooks);
|
||||
|
||||
test('it renders fields', async function (assert) {
|
||||
await render(hbs`
|
||||
<Form::Field::KeyValueList as |M|>
|
||||
<M.Legend>Label</M.Legend>
|
||||
<M.HelperText>Help</M.HelperText>
|
||||
<M.Error as |E|>
|
||||
<E.Message>Error!</E.Message>
|
||||
</M.Error>
|
||||
</Form::Field::KeyValueList>
|
||||
`);
|
||||
|
||||
assert.dom('tbody tr').exists({ count: 1 });
|
||||
assert.dom('legend').exists().hasText('Label');
|
||||
assert.dom('.hds-form-helper-text').exists().hasText('Help');
|
||||
assert.dom('.hds-form-error__message').exists().hasText('Error!');
|
||||
assert.dom('button').exists().hasText('Add');
|
||||
});
|
||||
|
||||
test('it renders multiple options', async function (assert) {
|
||||
this.options = [
|
||||
{ key: 'one', value: 'two' },
|
||||
{ key: 'three', value: 'four' },
|
||||
];
|
||||
|
||||
// Template block usage:
|
||||
await render(hbs`
|
||||
<Form::Field::KeyValueList
|
||||
@options={{this.options}}
|
||||
/>
|
||||
`);
|
||||
|
||||
assert.dom('tbody tr').exists({ count: 3 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
import {
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC,
|
||||
TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
} from 'api/models/credential-library';
|
||||
|
||||
module('Unit | Abilities | credential-library', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
let features;
|
||||
|
||||
hooks.beforeEach(function () {
|
||||
features = this.owner.lookup('service:features');
|
||||
features.enable('credential-library-vault-ssh-certificate');
|
||||
});
|
||||
|
||||
test('can read credential library type when authorized and feature is enabled', function (assert) {
|
||||
assert.expect(2);
|
||||
const canService = this.owner.lookup('service:can');
|
||||
const store = this.owner.lookup('service:store');
|
||||
const credentialLibrary = store.createRecord('credential-library', {
|
||||
authorized_actions: ['read'],
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.true(canService.can('read credential-library', credentialLibrary));
|
||||
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
assert.true(canService.can('read credential-library', credentialLibrary));
|
||||
});
|
||||
|
||||
test('cannot read credential library type when unauthorized and feature is enabled', function (assert) {
|
||||
assert.expect(2);
|
||||
const canService = this.owner.lookup('service:can');
|
||||
const store = this.owner.lookup('service:store');
|
||||
const credentialLibrary = store.createRecord('credential-library', {
|
||||
authorized_actions: [],
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.false(canService.can('read credential-library', credentialLibrary));
|
||||
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
assert.false(canService.can('read credential-library', credentialLibrary));
|
||||
});
|
||||
|
||||
test('cannot read credential library type when unauthorized and feature is disabled', function (assert) {
|
||||
assert.expect(2);
|
||||
const canService = this.owner.lookup('service:can');
|
||||
const store = this.owner.lookup('service:store');
|
||||
const featuresService = this.owner.lookup('service:features');
|
||||
featuresService.disable('credential-library-vault-ssh-certificate');
|
||||
const credentialLibrary = store.createRecord('credential-library', {
|
||||
authorized_actions: [],
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.false(canService.can('read credential-library', credentialLibrary));
|
||||
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
assert.false(canService.can('read credential-library', credentialLibrary));
|
||||
});
|
||||
|
||||
test('can read vault-generic but not vault-ssh-certificate when authorized and feature is disabled', function (assert) {
|
||||
assert.expect(2);
|
||||
const canService = this.owner.lookup('service:can');
|
||||
const store = this.owner.lookup('service:store');
|
||||
const featuresService = this.owner.lookup('service:features');
|
||||
featuresService.disable('credential-library-vault-ssh-certificate');
|
||||
const credentialLibrary = store.createRecord('credential-library', {
|
||||
authorized_actions: ['read'],
|
||||
type: TYPE_CREDENTIAL_LIBRARY_VAULT_SSH_CERTIFICATE,
|
||||
});
|
||||
assert.false(canService.can('read credential-library', credentialLibrary));
|
||||
credentialLibrary.type = TYPE_CREDENTIAL_LIBRARY_VAULT_GENERIC;
|
||||
assert.true(canService.can('read credential-library', credentialLibrary));
|
||||
});
|
||||
});
|
||||
@@ -4689,43 +4689,46 @@
|
||||
resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb"
|
||||
integrity sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==
|
||||
|
||||
"@hashicorp/design-system-components@^1.3.2":
|
||||
version "1.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/design-system-components/-/design-system-components-1.3.2.tgz#42d10b20bf93bdf4587012af8a4a944f3538272a"
|
||||
integrity sha512-aXm5pv9LurVFpoHTgOo87H2LxcdykSPMo88W2E/sw5dEEwJPZARI/Mg3H1i3EjR8Psc1COAozP5jpOhfer1b4w==
|
||||
"@hashicorp/design-system-components@^1.5.1":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/design-system-components/-/design-system-components-1.5.1.tgz#ed02dd834db2d254f2dc6c108f3520ddf3268de3"
|
||||
integrity sha512-P1kSd6t3V/cbAiWBWw/iib7WOZFDBIXFqbFP2UvT9LuZoP6tejrRfVy8Xhiy+NxCb2uVR543T3d8M/7E+N2e0Q==
|
||||
dependencies:
|
||||
"@hashicorp/design-system-tokens" "^1.2.0"
|
||||
"@hashicorp/ember-flight-icons" "^3.0.1"
|
||||
"@hashicorp/design-system-tokens" "^1.3.0"
|
||||
"@hashicorp/ember-flight-icons" "^3.0.2"
|
||||
dialog-polyfill "^0.5.6"
|
||||
ember-auto-import "^2.4.2"
|
||||
ember-cached-decorator-polyfill "^0.1.4"
|
||||
ember-cli-babel "^7.26.11"
|
||||
ember-cli-htmlbars "^6.1.0"
|
||||
ember-cli-sass "^10.0.1"
|
||||
ember-composable-helpers "^4.4.1"
|
||||
ember-focus-trap "^1.0.1"
|
||||
ember-keyboard "^8.1.0"
|
||||
ember-named-blocks-polyfill "^0.2.5"
|
||||
ember-style-modifier "^0.8.0"
|
||||
ember-truth-helpers "^3.0.0"
|
||||
sass "^1.43.4"
|
||||
|
||||
"@hashicorp/design-system-tokens@^1.2.0":
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/design-system-tokens/-/design-system-tokens-1.2.0.tgz#e83a69934c5c0c84de3c4572811e56543905bf39"
|
||||
integrity sha512-N74hb1mLImbY1NyH7Of4UDY7HBO5A4VSdXzXyLO2ZrB/WYIfX++uKjOMknca0lAAIMC7kqaYm3OIbND/sHbu8g==
|
||||
"@hashicorp/design-system-tokens@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/design-system-tokens/-/design-system-tokens-1.3.0.tgz#0a143f7fb20b72ac003e56c7408e23b0b55d45f9"
|
||||
integrity sha512-Re16f4tqyAApChk20Eev0xVWijCo8m3YYIo4A84tStTTkXk4d63lbY1MuebWaaAjcjzKGuyPh0w3u0SJWJFCUg==
|
||||
|
||||
"@hashicorp/ember-flight-icons@^3.0.1":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-flight-icons/-/ember-flight-icons-3.0.1.tgz#38895453a7994d7089bc058ccaa11f9c7eb9584e"
|
||||
integrity sha512-1gmzv3d//YzW87vd5FsJQpgq+addDdl3xgPkT1v9uVaDh7k8AZsz9IltymhP+Q62OIGHhx7GwagIwnwf3B6bAQ==
|
||||
"@hashicorp/ember-flight-icons@^3.0.2":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/ember-flight-icons/-/ember-flight-icons-3.0.2.tgz#8d884c07842a6c88df18ca680d7883a59222a2ba"
|
||||
integrity sha512-WomQg1hw/IHA1N9hC77WbTNazVXqu2RdRoaVCGT99NTXQ4S7Bw7vhHheR4JAgt10ksMZFI3X/bJVHxFfjUCkSQ==
|
||||
dependencies:
|
||||
"@hashicorp/flight-icons" "^2.11.1"
|
||||
"@hashicorp/flight-icons" "^2.12.0"
|
||||
ember-auto-import "^2.4.2"
|
||||
ember-cli-babel "^7.26.11"
|
||||
ember-cli-htmlbars "^6.1.0"
|
||||
|
||||
"@hashicorp/flight-icons@^2.11.1":
|
||||
version "2.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.11.1.tgz#0ef70fac396699fb1987afab9570786eda57e95b"
|
||||
integrity sha512-JD3/o1jtru9b2nzMeYYABcQ3TCcBx3wv0XjibdjQ18yhZ4b3zW6v0sl9eRDl+wbfVfBeXLXuGabt7ZXWNaPCCA==
|
||||
"@hashicorp/flight-icons@^2.12.0":
|
||||
version "2.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.12.0.tgz#48bc21f21678668ffe9147b181a2991d8b151fc7"
|
||||
integrity sha512-PhjTTHCjoq4EJirifbxLxnxXnCRf1NUAYZ1WnFW8i0yOmmax6fgjsJRPlf0VIGsR8R7isFpjuy6gJ5c7mNhE0w==
|
||||
|
||||
"@humanwhocodes/config-array@^0.5.0":
|
||||
version "0.5.0"
|
||||
@@ -11908,6 +11911,11 @@ dezalgo@^1.0.4:
|
||||
asap "^2.0.0"
|
||||
wrappy "1"
|
||||
|
||||
dialog-polyfill@^0.5.6:
|
||||
version "0.5.6"
|
||||
resolved "https://registry.yarnpkg.com/dialog-polyfill/-/dialog-polyfill-0.5.6.tgz#7507b4c745a82fcee0fa07ce64d835979719599a"
|
||||
integrity sha512-ZbVDJI9uvxPAKze6z146rmfUZjBqNEwcnFTVamQzXH+svluiV7swmVIGr7miwADgfgt1G2JQIytypM9fbyhX4w==
|
||||
|
||||
diff-sequences@^26.6.2:
|
||||
version "26.6.2"
|
||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1"
|
||||
@@ -13230,6 +13238,16 @@ ember-compatibility-helpers@^1.2.5:
|
||||
fs-extra "^9.1.0"
|
||||
semver "^5.4.1"
|
||||
|
||||
ember-composable-helpers@^4.4.1:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-composable-helpers/-/ember-composable-helpers-4.5.0.tgz#94febbdf4348e64f45f7a6f993f326e32540a61e"
|
||||
integrity sha512-XjpDLyVPsLCy6kd5dIxZonOECCO6AA5sY5Hr6tYUbJg3s5ghFAiFWaNcYraYC+fL2yPJQAswwpfwGlQORUJZkw==
|
||||
dependencies:
|
||||
"@babel/core" "^7.0.0"
|
||||
broccoli-funnel "2.0.1"
|
||||
ember-cli-babel "^7.26.3"
|
||||
resolve "^1.10.0"
|
||||
|
||||
ember-composable-helpers@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-composable-helpers/-/ember-composable-helpers-5.0.0.tgz#055bab3a3e234ab2917499b1465e968c253ca885"
|
||||
|
||||
Reference in new issue
Block a user