Skip to content

Commit

Permalink
hashicorp#6435 UI Obfuscated password input
Browse files Browse the repository at this point in the history
  • Loading branch information
hmalphettes committed Jun 3, 2020
1 parent 8e1ae33 commit 17e23c8
Show file tree
Hide file tree
Showing 21 changed files with 105 additions and 30 deletions.
26 changes: 18 additions & 8 deletions ui/app/components/secret-edit.js
@@ -1,14 +1,14 @@
import { isBlank, isNone } from '@ember/utils';
import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { computed, set } from '@ember/object';
import { alias, or } from '@ember/object/computed';
import { inject as service } from '@ember/service';
import { isBlank, isNone } from '@ember/utils';
import { task, waitForEvent } from 'ember-concurrency';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor';
import keys from 'vault/lib/keycodes';
import KVObject from 'vault/lib/kv-object';
import { maybeQueryRecord } from 'vault/macros/maybe-query-record';
import FocusOnInsertMixin from 'vault/mixins/focus-on-insert';
import WithNavToNearestAncestor from 'vault/mixins/with-nav-to-nearest-ancestor';

const LIST_ROUTE = 'vault.cluster.secrets.backend.list';
const LIST_ROOT_ROUTE = 'vault.cluster.secrets.backend.list-root';
Expand All @@ -19,6 +19,7 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
router: service(),
store: service(),
flashMessages: service(),
classNameBindings: ['showObfuscatedInputMode:obfuscated-input'],

// a key model
key: null,
Expand All @@ -41,13 +42,16 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
onDataChange() {},
onRefresh() {},
onToggleAdvancedEdit() {},
onToggleObfuscatedInput() {},

// did user request advanced mode
preferAdvancedEdit: false,
preferObfuscatedInput: false,

// use a named action here so we don't have to pass one in
// this will bubble to the route
toggleAdvancedEdit: 'toggleAdvancedEdit',
toggleObfuscatedInput: 'toggleObfuscatedInput',
error: null,

codemirrorString: null,
Expand Down Expand Up @@ -164,12 +168,15 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
if (this.isV2 && this.modelForData.failedServerRead) {
return true;
}
// if the model couldn't be read from the server
if (!this.isV2 && this.model.failedServerRead) {
return true;
}
return false;
}),
showObfuscatedInputMode: computed('preferObfuscatedInput', 'lastChange', function() {
return this.preferObfuscatedInput;
}),

showObfuscatedInputMode: computed('preferObfuscatedInput', 'lastChange', function() {
return this.preferObfuscatedInput;
}),

transitionToRoute() {
return this.router.transitionTo(...arguments);
Expand Down Expand Up @@ -346,6 +353,9 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
toggleAdvanced(bool) {
this.onToggleAdvancedEdit(bool);
},
toggleObfuscated(bool) {
this.onToggleObfuscatedInput(bool);
},

codemirrorUpdated(val, codemirror) {
this.set('error', null);
Expand Down
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/create.js
Expand Up @@ -15,5 +15,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscatedInput', bool);
this.get('backendController').set('preferObfuscatedInput', bool);
},
},
});
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/edit.js
Expand Up @@ -19,5 +19,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscatedInput', bool);
this.get('backendController').set('preferObfuscatedInput', bool);
},
},
});
4 changes: 4 additions & 0 deletions ui/app/controllers/vault/cluster/secrets/backend/show.js
Expand Up @@ -21,5 +21,9 @@ export default Controller.extend(BackendCrumbMixin, {
this.set('preferAdvancedEdit', bool);
this.get('backendController').set('preferAdvancedEdit', bool);
},
toggleObfuscatedInput(bool) {
this.set('preferObfuscated', bool);
this.get('backendController').set('preferObfuscated', bool);
},
},
});
8 changes: 6 additions & 2 deletions ui/app/models/auth-config/aws/client.js
Expand Up @@ -6,8 +6,12 @@ import fieldToAttrs from 'vault/utils/field-to-attrs';
const { attr } = DS;

export default AuthConfig.extend({
secretKey: attr('string'),
accessKey: attr('string'),
secretKey: attr('string', {
sensitive: true,
}),
accessKey: attr('string', {
sensitive: true,
}),
endpoint: attr('string', {
label: 'EC2 Endpoint',
}),
Expand Down
2 changes: 2 additions & 0 deletions ui/app/models/auth-config/azure.js
Expand Up @@ -19,9 +19,11 @@ export default AuthConfig.extend({
label: 'Client ID',
helpText:
'The client ID for credentials to query the Azure APIs. Currently read permissions to query compute resources are required.',
sensitive: true,
}),
clientSecret: attr('string', {
helpText: 'The client secret for credentials to query the Azure APIs',
sensitive: true,
}),

googleCertsEndpoint: attr('string'),
Expand Down
1 change: 1 addition & 0 deletions ui/app/models/auth-config/jwt.js
Expand Up @@ -20,6 +20,7 @@ export default AuthConfig.extend({

oidcClientSecret: attr('string', {
label: 'OIDC client secret',
sensitive: true,
}),
oidcDiscoveryCaPem: attr('string', {
label: 'OIDC discovery CA PEM',
Expand Down
1 change: 1 addition & 0 deletions ui/app/models/auth-config/kubernetes.js
Expand Up @@ -22,6 +22,7 @@ export default AuthConfig.extend({
tokenReviewerJwt: attr('string', {
helpText:
'A service account JWT used to access the TokenReview API to validate other JWTs during login. If not set the JWT used for login will be used to access the API',
sensitive: true,
}),

pemKeys: attr({
Expand Down
3 changes: 3 additions & 0 deletions ui/app/routes/vault/cluster/secrets/backend/secret-edit.js
Expand Up @@ -231,6 +231,8 @@ export default Route.extend(UnloadModelRoute, {
let backend = this.enginePathParam();
const preferAdvancedEdit =
this.controllerFor('vault.cluster.secrets.backend').get('preferAdvancedEdit') || false;
const preferObfuscatedInput =
this.controllerFor('vault.cluster.secrets.backend').get('preferObfuscatedInput') || false;
const backendType = this.backendType();
model.secret.setProperties({ backend });
controller.setProperties({
Expand All @@ -244,6 +246,7 @@ export default Route.extend(UnloadModelRoute, {
.replace('-root', ''),
backend,
preferAdvancedEdit,
preferObfuscatedInput,
backendType,
});
},
Expand Down
10 changes: 10 additions & 0 deletions ui/app/styles/app.scss
@@ -1,3 +1,13 @@
@import 'ember-basic-dropdown';
@import 'ember-power-select';
@import './core';

@mixin font-face($name) {
@font-face {
font-family: $name;
src: url("/ui/fonts/#{$name}.woff2") format("woff2"),
url("/ui/fonts/#{$name}.woff") format("woff");
}
}

@include font-face('ObfuscatedPasswordFont');
6 changes: 6 additions & 0 deletions ui/app/styles/components/masked-input.scss
Expand Up @@ -24,6 +24,12 @@
line-height: 2.5;
}

.obfuscated-input .masked-input .input {
font-size: 9px;
font-family: ObfuscatedPasswordFont;
line-height: 2.5;
}

.masked-input.display-only .masked-value {
order: 1;
}
Expand Down
2 changes: 1 addition & 1 deletion ui/app/templates/components/auth-config-form/config.hbs
@@ -1,5 +1,5 @@
<form {{action (perform saveModel) on="submit"}}>
<div class="box is-sideless is-fullwidth is-marginless">
<div class="box is-sideless is-fullwidth is-marginless obfuscated-input">
<NamespaceReminder @mode="save" @noun="Auth Method" />
{{message-error model=model}}
{{#if model.attrs}}
Expand Down
12 changes: 11 additions & 1 deletion ui/app/templates/components/secret-edit.hbs
Expand Up @@ -26,6 +26,16 @@
<Toolbar>
{{#unless (and (eq mode 'show') isWriteWithoutRead)}}
<ToolbarFilters>
<Toggle
@name="obfuscated"
@status="success"
@size="small"
@disabled={{and (eq mode 'show') secretDataIsAdvanced}}
@checked={{showObfuscatedInputMode}}
@onChange={{action "toggleObfuscated"}}
>
<span class="has-text-grey">Obfuscated Input</span>
</Toggle>
<Toggle
@name="json"
@status="success"
Expand Down Expand Up @@ -188,4 +198,4 @@
</ToolbarActions>
</Toolbar>

{{partial partialName}}
{{partial partialName}}
5 changes: 3 additions & 2 deletions ui/app/templates/partials/secret-form-create.hbs
@@ -1,10 +1,10 @@
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}}" onsubmit={{action "createOrUpdateKey" "create"}}>
<form class="{{if showAdvancedMode 'advanced-edit' 'simple-edit'}} {{if showObfuscatedInput 'obfuscated-input'}}" onsubmit={{action "createOrUpdateKey" "create"}}>
<div class="field box is-fullwidth is-sideless is-marginless">
<NamespaceReminder @mode="create" @noun="secret" />
<MessageError @model={{modelForData}} @errorMessage={{error}} />
<label class="is-label" for="kv-key">Path for this secret</label>
<p class="control is-expanded">
{{input
{{input
autocomplete="off"
spellcheck="false"
data-test-secret-path="true"
Expand All @@ -21,6 +21,7 @@
</div>
<SecretEditDisplay
@showAdvancedMode={{showAdvancedMode}}
@showObfuscatedInputMode={{showObfuscatedInputMode}}
@codemirrorString={{codemirrorString}}
@secretData={{secretData}}
@isV2={{isV2}}
Expand Down
1 change: 1 addition & 0 deletions ui/app/templates/partials/secret-form-edit.hbs
Expand Up @@ -13,6 +13,7 @@
{{/if}}
<SecretEditDisplay
@showAdvancedMode={{showAdvancedMode}}
@showObfuscatedInputMode={{showObfuscatedInputMode}}
@codemirrorString={{codemirrorString}}
@secretData={{secretData}}
@isV2={{isV2}}
Expand Down
Expand Up @@ -7,7 +7,9 @@
capabilities=capabilities
onRefresh=(action "refresh")
onToggleAdvancedEdit=(action "toggleAdvancedEdit")
onToggleObfuscatedInput=(action "toggleObfuscatedInput")
initialKey=initialKey
baseKey=baseKey
preferAdvancedEdit=preferAdvancedEdit
preferObfuscatedInput=preferObfuscatedInput
}}
1 change: 1 addition & 0 deletions ui/config/environment.js
Expand Up @@ -57,6 +57,7 @@ module.exports = function(environment) {
ENV.contentSecurityPolicyHeader = 'Content-Security-Policy';
ENV.contentSecurityPolicyMeta = true;
ENV.contentSecurityPolicy = {
'font-src': ["'self'"],
'connect-src': ["'self'"],
'img-src': ["'self'", 'data:'],
'form-action': ["'none'"],
Expand Down
41 changes: 26 additions & 15 deletions ui/lib/core/addon/templates/components/masked-input.hbs
@@ -1,21 +1,32 @@
<div class="masked-input {{if shouldObscure "masked"}} {{if displayOnly "display-only"}} {{if allowCopy "allow-copy"}}"
data-test-masked-input data-test-field>
{{#if displayOnly}}
<pre class="masked-value display-only is-word-break">{{displayValue}}</pre>
<div class="masked-input {{if shouldObscure "masked"}} {{if displayOnly "display-only"}} {{if allowCopy "allow-copy"}}" data-test-masked-input data-test-field>
{{#if displayOnly}}
<pre class="masked-value display-only is-word-break">{{displayValue}}</pre>
{{else}}
<textarea class="input masked-value" rows=1 wrap="off" placeholder={{placeholder}}
onfocus={{action (mut isFocused) true}} onblur={{action (mut isFocused) false}} onkeydown={{action onKeyDown}}
onchange={{action "updateValue"}} value={{readonly displayValue}} data-test-textarea />
{{/if}}
<textarea
class="input masked-value"
rows=1
wrap="off"
spellcheck="false"
placeholder={{placeholder}}
onfocus={{action (mut isFocused) true}}
onblur={{action (mut isFocused) false}}
onkeydown={{action onKeyDown}}
onchange={{action "updateValue"}}
value={{readonly displayValue}}
data-test-textarea
/>
{{/if}}
{{#if allowCopy}}
<CopyButton @clipboardText={{value}} @success={{success}} class="copy-button button {{if displayOnly "is-compact"}}"
data-test-copy-button>
<CopyButton
@clipboardText={{value}}
@success={{success}}
class="copy-button button {{if displayOnly "is-compact"}}"
data-test-copy-button
>
<Icon @glyph="copy-action" aria-hidden="Copy value" />
</CopyButton>
{{/if}}
<button {{action "toggleMask"}}
class="{{if (eq value "") "has-text-grey"}} masked-input-toggle button {{if displayOnly "is-compact"}}"
data-test-button>
<button {{action "toggleMask"}} class="{{if (eq value "") "has-text-grey"}} masked-input-toggle button {{if displayOnly "is-compact"}}" data-test-button>
<Icon @glyph={{if shouldObscure "visibility-hide" "visibility-show"}} aria-hidden="true" />
</button>
</div>
</button>
</div>
Binary file added ui/public/fonts/ObfuscatedPasswordFont.woff
Binary file not shown.
Binary file added ui/public/fonts/ObfuscatedPasswordFont.woff2
Binary file not shown.
2 changes: 1 addition & 1 deletion vault/ui.go
Expand Up @@ -32,7 +32,7 @@ type UIConfig struct {
// NewUIConfig creates a new UI config
func NewUIConfig(enabled bool, physicalStorage physical.Backend, barrierStorage logical.Storage) *UIConfig {
defaultHeaders := http.Header{}
defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'")
defaultHeaders.Set("Content-Security-Policy", "default-src 'none'; connect-src 'self'; img-src 'self' data:; script-src 'self'; style-src 'unsafe-inline' 'self'; form-action 'none'; frame-ancestors 'none'; font-src 'self'")
defaultHeaders.Set("Service-Worker-Allowed", "/")

return &UIConfig{
Expand Down

0 comments on commit 17e23c8

Please sign in to comment.