Merge pull request #619 from hashicorp/feature-more-session-creds

Feature more session creds
This commit is contained in:
Susmitha Girumala authored and GitHub committed 2021-06-25 11:57:08 -04:00
commit 86ebba9394
12 files changed
+346 -83

No files matched your search

+81
View File
@@ -2,14 +2,88 @@ import GeneratedSessionModel from '../generated/models/session';
import { computed } from '@ember/object';
import { equal } from '@ember/object/computed';
import { tracked } from '@glimmer/tracking';
import { A } from '@ember/array';
import { typeOf } from '@ember/utils';
/**
*
*/
class SessionCredential {
// =classes
/**
*
*/
static Library = class Library {
id;
name;
description;
type;
constructor(id, name, description, type) {
this.id = id;
this.name = name;
this.description = description;
this.type = type;
}
};
/**
*
*/
static SecretItem = class SecretItem {
key;
value;
constructor(key, value) {
this.key = key;
this.value = value;
}
};
// =attributes
rawCredential;
library;
purpose;
#rawSecretValue;
/**
* The credential secret as an array of key/value objects.
* @type {SessionCredential.SecretItem[]}
*/
get secrets() {
if (typeOf(this.#rawSecretValue) === 'object') {
return Object.keys(this.#rawSecretValue).map(
(key) =>
new SessionCredential.SecretItem(key, this.#rawSecretValue[key])
);
} else {
return [new SessionCredential.SecretItem('secret', this.#rawSecretValue)];
}
}
// =methods
constructor(cred) {
const { id, name, description, type } = cred.credential_library;
this.library = new SessionCredential.Library(id, name, description, type);
this.#rawSecretValue = cred.secret;
this.rawCredential = cred;
this.purpose = cred.purpose;
}
}
export default class SessionModel extends GeneratedSessionModel {
// =attributes
// These transient attributes are used for values that come from the CLI
// `connect` command as triggered by Desktop. These attributes represent
// transient read-only values.
@tracked acknowledged;
@tracked started_desktop_client;
@tracked proxy_address;
@tracked proxy_port;
credentials = A();
@equal('status', 'active') isActive;
@equal('status', 'pending') isPending;
@@ -43,6 +117,13 @@ export default class SessionModel extends GeneratedSessionModel {
// =methods
/**
*
*/
addCredential(cred) {
this.credentials.pushObject(new SessionCredential(cred));
}
/**
* Cancels the session via the `cancel` method.
* See serializer and adapter for more information.
+54
View File
@@ -58,6 +58,60 @@ export default function initializeMockIPC(server) {
port: datatype.number(),
protocol: type,
session_id: newSession.id,
credentials: [
{
credential_library: {
id: 'clvlt_4cvscMTl0N',
name: 'Library Name',
description: 'Library Description',
credential_store_id: 'csvlt_Q1HFGt7Jpm',
type: 'vault',
},
purpose: 'application',
secret: 'just-a-random-string',
},
{
credential_library: {
id: 'clvlt_4cvscMTl0N',
name: 'Library Name',
description: 'Library Description',
credential_store_id: 'csvlt_Q1HFGt7Jpm',
type: 'vault',
},
purpose: 'ingress',
secret: {
key1: 'value 1',
key2: true,
key3: 'test',
},
},
{
credential_library: {
id: 'clvlt_9KWscxpcY7',
name: 'Library Name',
description: 'Library Description',
credential_store_id: 'csvlt_Q1HFGt7Jpm',
type: 'vault',
},
purpose: 'application',
secret: 'just-a-random-string',
},
{
credential_library: {
id: 'clvlt_9KWscxpcY7',
name: 'Library Name',
description: 'Library Description',
credential_store_id: 'csvlt_Q1HFGt7Jpm',
type: 'vault',
},
purpose: 'egress',
secret: {
key1: 'value 1',
key2: true,
key3: 'test',
},
},
],
};
}
+4
View File
@@ -108,3 +108,7 @@ email:
label: Email
full_name:
label: Full Name
key:
label: Key
value:
label: Value
@@ -147,6 +147,17 @@ session:
description: No active sessions available to display yet.
actions:
connect: Connect
credential:
title: Credential
title_plural: Credentials
secret:
label: Secret
credential_library:
title: Credential Library
actions:
raw-api:
show: View raw API output
hide: Hide raw API output
org:
title: Org
title_plural: Orgs
@@ -256,3 +267,4 @@ target:
add:
title: Add Host Sets
description: Select host sets to assign to this target.
secret: ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■ ■
@@ -1,5 +1,4 @@
<div class='hidden-secret'>
{{! TODO: The span wrap within a div because later we will add the toggle secret icon/button}}
<Copyable
@text={{@secret}}
@buttonText={{t 'actions.copy-to-clipboard'}}
+8
View File
@@ -0,0 +1,8 @@
import { helper } from '@ember/component/helper';
/**
* Helper to convert json object to string for display purposes.
*/
export default helper(function rawJson([json]) {
return JSON.stringify(json, null, ' ');
});
@@ -109,7 +109,7 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
const connectionDetails = await this.ipc.invoke('connect', options);
// Associate the connection details with the session
const { session_id, address, port } = connectionDetails;
const { session_id, address, port, credentials } = connectionDetails;
const session = await this.store.findRecord('session', session_id);
// Flag the session has been open in the desktop client
@@ -119,6 +119,9 @@ export default class ScopesScopeProjectsTargetsRoute extends Route {
// we don't have to manually persist the proxy details.
session.proxy_address = address;
session.proxy_port = port;
if (credentials) {
credentials.forEach(cred => session.addCredential(cred));
}
} catch(e) {
this.confirm.confirm(e.message, { isConnectError: true })
// Retry
+32 -4
View File
@@ -395,19 +395,30 @@ $chevron-down: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231
}
&:not([open]) {
.reveal-trigger-text-close {
background: $chevron-down no-repeat right center;
padding-right: sizing.rems(l);
}
.reveal-trigger-text-open {
display: none;
}
}
&[open] {
.reveal-trigger {
background: url($chevron-up) no-repeat right center;
.reveal-trigger-text-open {
background: $chevron-up no-repeat right center;
padding-right: sizing.rems(l);
}
.reveal-trigger-text-close {
display: none;
}
pre {
white-space: pre-wrap;
word-break: break-all;
}
}
}
@@ -422,8 +433,8 @@ $chevron-down: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231
align-items: center;
border-bottom: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
display: flex;
justify-content: flex-start;
padding: sizing.rems(s) sizing.rems(m);
justify-content: space-between;
padding: sizing.rems(s) sizing.rems(s) sizing.rems(s) sizing.rems(m);
h2 {
@include type.type(s, normal);
@@ -445,6 +456,23 @@ $chevron-down: url("data:image/svg+xml;utf-8,<svg viewBox='0 0 24 24' fill='%231
border-top: sizing.rems(xxxxs) solid var(--ui-gray-subtler-3);
padding: sizing.rems(s);
}
// Allow text wrapping for copyable content in table
.rose-table .copyable {
white-space: inherit;
word-break: break-word;
}
}
.hidden-secret {
display: flex;
align-items: center;
justify-content: space-between;
color: var(--ui-gray-subtler-2);
.rose-button-ghost {
color: var(--ui-gray-subtler-1);
}
}
// Hidden secret component (app/components/hidden-secret)
@@ -18,71 +18,122 @@
@dismissButtonText={{t 'actions.ok'}}
>
<:body>
<h3>{{t 'resources.target.messages.proxy_url.title'}}
(<code>{{t 'resources.target.types.tcp'}}</code>)</h3>
<h3>
{{t 'resources.target.messages.proxy_url.title'}}
({{t 'resources.target.types.tcp'}})
</h3>
<p>
<Copyable
@text={{session.proxy}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
<code>{{session.proxy_address}}:{{session.proxy_port}}</code>
<code>{{session.proxy}}</code>
</Copyable>
</p>
{{!-- <Rose::Separator />
{{#if session.credentials}}
<Rose::Separator />
<h3>Credentials</h3>
<h3>{{t 'resources.session.credential.title_plural'}}</h3>
<Card @heading="Credentials" @icon='app-icons/credentials'>
<:header>
<Rose::Dropdown
@style="condensed"
@text="Credential library details"
@showCaret={{true}}
as |dropdown|
{{#each session.credentials as |credential|}}
<Card
@heading={{concat
(t 'resources.session.credential.title')
' ('
credential.purpose
')'
}}
@icon='app-icons/credential'
>
</Rose::Dropdown>
</:header>
<:body>
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>Key</row.headerCell>
<row.headerCell colspan='2'>Value</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
<body.row as |row|>
<row.headerCell>foo</row.headerCell>
<row.cell>Value</row.cell>
</body.row>
<body.row as |row|>
<row.headerCell>ttl</row.headerCell>
<row.cell>Test</row.cell>
</body.row>
</table.body>
</Rose::Table>
</:body>
<:footer>
<Reveal
@textWhenOpen='Hide content'
@textWhenClose='View content'
>
<pre>
<code>
{
"auth": null,
"lease_duration": 3600,
"lease_id": "",
"renewable": false
}
</code>
</pre>
</Reveal>
</:footer>
</Card> --}}
<:header>
<Rose::Dropdown
@text={{t
'resources.session.credential.credential_library.title'
}}
@style='condensed'
@dropdownRight={{true}}
@ignoreClickInside={{true}}
as |dropdown|
>
<dropdown.key-value>
<:key>{{t 'form.id.label'}}</:key>
<:value>
<Copyable
@text={{credential.library.id}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
<code>{{credential.library.id}}</code>
</Copyable>
</:value>
</dropdown.key-value>
<dropdown.key-value>
<:key>{{t 'form.type.label'}}</:key>
<:value>{{credential.library.type}}</:value>
</dropdown.key-value>
{{#if credential.library.name}}
<dropdown.key-value>
<:key>{{t 'form.name.label'}}</:key>
<:value>{{credential.library.name}}</:value>
</dropdown.key-value>
{{/if}}
{{#if credential.library.description}}
<dropdown.key-value>
<:key>{{t 'form.description.label'}}</:key>
<:value>{{credential.library.description}}</:value>
</dropdown.key-value>
{{/if}}
</Rose::Dropdown>
</:header>
<:body>
{{#if credential.secrets}}
<Rose::Table @style='condensed' as |table|>
<table.header as |header|>
<header.row as |row|>
<row.headerCell>{{t
'form.key.label'
}}</row.headerCell>
<row.headerCell colspan='2'>{{t
'form.value.label'
}}</row.headerCell>
</header.row>
</table.header>
<table.body as |body|>
{{#each credential.secrets as |secret|}}
<body.row as |row|>
<row.headerCell>{{secret.key}}</row.headerCell>
<row.cell colspan='2'>
<Copyable
@text={{secret.value}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
{{secret.value}}
</Copyable>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
{{/if}}
</:body>
<:footer>
<Reveal
@textWhenOpen={{t
'resources.session.credential.actions.raw-api.hide'
}}
@textWhenClose={{t
'resources.session.credential.actions.raw-api.show'
}}
>
<pre>{{raw-json credential.rawCredential}}</pre>
</Reveal>
</:footer>
</Card>
{{/each}}
{{/if}}
</:body>
<:footer>
<div>
@@ -93,15 +144,6 @@
{{t 'actions.close'}}
</Rose::Button>
</div>
<div>
{{! <Rose::LinkButton
@route='index'
@style='inline-link-action'
@iconRight='chevron-right'
>
</Rose::LinkButton> }}
</div>
</:footer>
</DialogDetail>
{{/unless}}
+4
View File
@@ -48,6 +48,10 @@ const extract = (artifactPath, destination) => {
module.exports = {
setup: async () => {
if (process.env.BYPASS_CLI_SETUP) {
console.warn('WARNING: Bypassing cli setup');
return;
}
const artifactPath = await downloadArtifact(artifactVersion);
await extract(artifactPath, artifactDestination);
}
@@ -0,0 +1,19 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Helper | raw-json', function (hooks) {
setupRenderingTest(hooks);
test('it renders', async function (assert) {
this.set('inputValue', {});
await render(hbs`{{raw-json inputValue}}`);
assert.equal(this.element.textContent.trim(), '{}');
this.set('inputValue', { key: 'value', nestedKey: { key: 'value' } });
assert.equal(
this.element.textContent.trim(),
`{\n "key": "value",\n "nestedKey": {\n "key": "value"\n }\n}`
);
});
});
+24 -15
View File
@@ -3671,7 +3671,7 @@ agent-base@4, agent-base@^4.3.0:
dependencies:
es6-promisify "^5.0.0"
agent-base@6:
agent-base@6, agent-base@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
@@ -6360,9 +6360,9 @@ caniuse-api@^3.0.0:
lodash.uniq "^4.5.0"
caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001219:
version "1.0.30001235"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001235.tgz#ad5ca75bc5a1f7b12df79ad806d715a43a5ac4ed"
integrity sha512-zWEwIVqnzPkSAXOUlQnPW2oKoYb2aLQ4Q5ejdjBcnH63rfypaW34CxaeBn1VMya2XaEU3P/R2qHpWyj+l0BT1A==
version "1.0.30001239"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001239.tgz#66e8669985bb2cb84ccb10f68c25ce6dd3e4d2b8"
integrity sha512-cyBkXJDMeI4wthy8xJ2FvDU6+0dtcZSJW3voUF8+e9f1bBeuvyZfc3PNbkOETyhbR+dGCPzn9E7MA3iwzusOhQ==
capture-exit@^2.0.0:
version "2.0.0"
@@ -15671,7 +15671,16 @@ npm-package-arg@^7.0.0:
semver "^5.6.0"
validate-npm-package-name "^3.0.0"
npm-package-arg@^8.0.0, npm-package-arg@^8.1.0:
npm-package-arg@^8.0.0:
version "8.1.5"
resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.5.tgz#3369b2d5fe8fdc674baa7f1786514ddc15466e44"
integrity sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==
dependencies:
hosted-git-info "^4.0.1"
semver "^7.3.4"
validate-npm-package-name "^3.0.0"
npm-package-arg@^8.1.0:
version "8.1.4"
resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-8.1.4.tgz#8001cdbc4363997b8ef6c6cf7aaf543c5805879d"
integrity sha512-xLokoCFqj/rPdr3LvcdDL6Kj6ipXGEDHD/QGpzwU6/pibYUOXmp5DBmg76yukFyx4ZDbrXNOTn+BPyd8TD4Jlw==
@@ -19098,11 +19107,11 @@ socks-proxy-agent@^4.0.0:
socks "~2.3.2"
socks-proxy-agent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.0.tgz#7c0f364e7b1cf4a7a437e71253bed72e9004be60"
integrity sha512-lEpa1zsWCChxiynk+lCycKuC502RxDWLKJZoIhnxrWNjLSDGYRFflHA1/228VkRcnv9TIb8w98derGbpKxJRgA==
version "5.0.1"
resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e"
integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ==
dependencies:
agent-base "6"
agent-base "^6.0.2"
debug "4"
socks "^2.3.3"
@@ -21634,9 +21643,9 @@ write@^0.2.1:
mkdirp "^0.5.1"
ws@^7.4.5, ws@^7.4.6, ws@~7.4.2:
version "7.4.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c"
integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==
version "7.5.0"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691"
integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw==
x-is-string@^0.1.0:
version "0.1.0"
@@ -21744,9 +21753,9 @@ yaml@^1.10.0, yaml@^1.7.2:
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@20.2.4, yargs-parser@^15.0.1, yargs-parser@^18.1.2, yargs-parser@^20.0.0, yargs-parser@^20.2.2, yargs-parser@^20.2.3, yargs-parser@^7.0.0, yargs-parser@^9.0.2, yargs-parser@~20.2.7:
version "20.2.7"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
version "20.2.9"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
yargs-unparser@2.0.0:
version "2.0.0"