Skip to content

Commit

Permalink
UI - unauthed login methods (#4854)
Browse files Browse the repository at this point in the history
* fetch auth methods when going to the auth route and pass them to the auth form component

* add boolean editType for form-fields

* look in the data hash in the serializer

* remove renderInPlace for info-tooltips as it does something goofy with widths

* add new fields for auth methods

* fix console refresh command on routes that use lazyPaginatedQuery

* add wrapped_token param that logs you in via the token backend and show other backends if your list contains supported ones

* handle casing when looking up supported backends

* change listingVisibility to match the new API

* move wrapped_token up to the vault route level so it works from the app root
  • Loading branch information
meirish authored Jul 5, 2018
1 parent 52a6ea3 commit 87d70fb
Show file tree
Hide file tree
Showing 28 changed files with 449 additions and 130 deletions.
2 changes: 1 addition & 1 deletion ui/app/adapters/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export default DS.RESTAdapter.extend({
},

_preRequest(url, options) {
const token = this.get('auth.currentToken');
const token = options.clientToken || this.get('auth.currentToken');
if (token && !options.unauthenticated) {
options.headers = Ember.assign(options.headers || {}, {
'X-Vault-Token': token,
Expand Down
17 changes: 16 additions & 1 deletion ui/app/adapters/auth-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,22 @@ export default ApplicationAdapter.extend({
return 'mounts/auth';
},

findAll() {
findAll(store, type, sinceToken, snapshotRecordArray) {
let isUnauthenticated = Ember.get(snapshotRecordArray || {}, 'adapterOptions.unauthenticated');
if (isUnauthenticated) {
let url = `/${this.urlPrefix()}/internal/ui/mounts`;
return this.ajax(url, 'GET', {
unauthenticated: true,
})
.then(result => {
return {
data: result.data.auth,
};
})
.catch(() => {
return [];
});
}
return this.ajax(this.url(), 'GET').catch(e => {
if (e instanceof DS.AdapterError) {
Ember.set(e, 'policyPath', 'sys/auth');
Expand Down
4 changes: 2 additions & 2 deletions ui/app/adapters/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ export default ApplicationAdapter.extend({
},

toolAction(action, data, options = {}) {
const { wrapTTL } = options;
const { wrapTTL, clientToken } = options;
const url = this.toolUrlFor(action);
const ajaxOptions = wrapTTL ? { data, wrapTTL } : { data };
const ajaxOptions = wrapTTL ? { data, wrapTTL, clientToken } : { data, clientToken };
return this.ajax(url, 'POST', ajaxOptions);
},
});
116 changes: 94 additions & 22 deletions ui/app/components/auth-form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Ember from 'ember';
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';
import { task } from 'ember-concurrency';
const BACKENDS = supportedAuthBackends();
const { computed, inject, get } = Ember;

Expand All @@ -11,57 +12,125 @@ const DEFAULTS = {

export default Ember.Component.extend(DEFAULTS, {
classNames: ['auth-form'],
routing: inject.service('-routing'),
router: inject.service(),
auth: inject.service(),
flashMessages: inject.service(),
store: inject.service(),
csp: inject.service('csp-event'),

// set during init and potentially passed in via a query param
selectedAuth: null,
methods: null,
cluster: null,
redirectTo: null,

didRender() {
this._super(...arguments);
// on very narrow viewports the active tab may be overflowed, so we scroll it into view here
this.$('li.is-active').get(0).scrollIntoView();
let activeEle = this.element.querySelector('li.is-active');
if (activeEle) {
activeEle.scrollIntoView();
}
// this is here because we're changing the `with` attr and there's no way to short-circuit rendering,
// so we'll just nav -> get new attrs -> re-render
if (!this.get('selectedAuth') || (this.get('selectedAuth') && !this.get('selectedAuthBackend'))) {
this.get('router').replaceWith('vault.cluster.auth', this.get('cluster.name'), {
queryParams: {
with: this.firstMethod(),
wrappedToken: this.get('wrappedToken'),
},
});
}
},

firstMethod() {
let firstMethod = this.get('methodsToShow.firstObject');
// prefer backends with a path over those with a type
return get(firstMethod, 'path') || get(firstMethod, 'type');
},

didReceiveAttrs() {
this._super(...arguments);
let newMethod = this.get('selectedAuthType');
let oldMethod = this.get('oldSelectedAuthType');
let token = this.get('wrappedToken');
let newMethod = this.get('selectedAuth');
let oldMethod = this.get('oldSelectedAuth');

if (oldMethod && oldMethod !== newMethod) {
this.resetDefaults();
}
this.set('oldSelectedAuthType', newMethod);
this.set('oldSelectedAuth', newMethod);

if (token) {
this.get('unwrapToken').perform(token);
}
},

resetDefaults() {
this.setProperties(DEFAULTS);
},

cluster: null,
redirectTo: null,

selectedAuthType: 'token',
selectedAuthBackend: Ember.computed('selectedAuthType', function() {
return BACKENDS.findBy('type', this.get('selectedAuthType'));
}),
selectedAuthIsPath: computed.match('selectedAuth', /\/$/),
selectedAuthBackend: Ember.computed(
'allSupportedMethods',
'selectedAuth',
'selectedAuthIsPath',
function() {
let methods = this.get('allSupportedMethods');
let keyIsPath = this.get('selectedAuthIsPath');
let findKey = keyIsPath ? 'path' : 'type';
return methods.findBy(findKey, this.get('selectedAuth'));
}
),

providerPartialName: Ember.computed('selectedAuthType', function() {
const type = Ember.String.dasherize(this.get('selectedAuthType'));
return `partials/auth-form/${type}`;
providerPartialName: computed('selectedAuthBackend', function() {
let type = this.get('selectedAuthBackend.type') || 'token';
type = type.toLowerCase();
let templateName = Ember.String.dasherize(type);
return `partials/auth-form/${templateName}`;
}),

hasCSPError: computed.alias('csp.connectionViolations.firstObject'),

cspErrorText: `This is a standby Vault node but can't communicate with the active node via request forwarding. Sign in at the active node to use the Vault UI.`,

allSupportedMethods: computed('methodsToShow', 'hasMethodsWithPath', function() {
let hasMethodsWithPath = this.get('hasMethodsWithPath');
let methodsToShow = this.get('methodsToShow');
return hasMethodsWithPath ? methodsToShow.concat(BACKENDS) : methodsToShow;
}),

hasMethodsWithPath: computed('methodsToShow', function() {
return this.get('methodsToShow').isAny('path');
}),
methodsToShow: computed('methods', 'methods.[]', function() {
let methods = this.get('methods') || [];
let shownMethods = methods.filter(m =>
BACKENDS.find(b => get(b, 'type').toLowerCase() === get(m, 'type').toLowerCase())
);
return shownMethods.length ? shownMethods : BACKENDS;
}),

unwrapToken: task(function*(token) {
// will be using the token auth method, so set it here
this.set('selectedAuth', 'token');
let adapter = this.get('store').adapterFor('tools');
try {
let response = yield adapter.toolAction('unwrap', null, { clientToken: token });
this.set('token', response.auth.client_token);
this.send('doSubmit');
} catch (e) {
this.set('error', `Token unwrap failed: ${e.errors[0]}`);
}
}),

handleError(e) {
this.set('loading', false);

let errors = e.errors.map(error => {
if (error.detail) {
return error.detail;
}
return error;
});

this.set('error', `Authentication failed: ${errors.join('.')}`);
},

Expand All @@ -73,19 +142,22 @@ export default Ember.Component.extend(DEFAULTS, {
error: null,
});
let targetRoute = this.get('redirectTo') || 'vault.cluster';
let backend = this.get('selectedAuthBackend');
let path = this.get('customPath');
let attributes = get(backend, 'formAttributes');
let backend = this.get('selectedAuthBackend') || {};
let path = get(backend, 'path') || this.get('customPath');
let backendMeta = BACKENDS.find(
b => get(b, 'type').toLowerCase() === get(backend, 'type').toLowerCase()
);
let attributes = get(backendMeta, 'formAttributes');

data = Ember.assign(data, this.getProperties(...attributes));
if (this.get('useCustomPath') && path) {
if (get(backend, 'path') || (this.get('useCustomPath') && path)) {
data.path = path;
}
const clusterId = this.get('cluster.id');
this.get('auth').authenticate({ clusterId, backend: get(backend, 'type'), data }).then(
({ isRoot }) => {
this.set('loading', false);
const transition = this.get('routing.router').transitionTo(targetRoute);
const transition = this.get('router').transitionTo(targetRoute);
if (isRoot) {
transition.followRedirects().then(() => {
this.get('flashMessages').warning(
Expand Down
2 changes: 2 additions & 0 deletions ui/app/components/console/ui-panel.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default Ember.Component.extend({
isFullscreen: false,
console: inject.service(),
router: inject.service(),
store: inject.service(),
inputValue: null,
log: computed.alias('console.log'),

Expand Down Expand Up @@ -86,6 +87,7 @@ export default Ember.Component.extend({
let route = owner.lookup(`route:${routeName}`);

try {
this.get('store').clearAllDatasets();
yield route.refresh();
this.logAndOutput(null, { type: 'success', content: 'The current screen has been refreshed!' });
} catch (error) {
Expand Down
5 changes: 5 additions & 0 deletions ui/app/components/form-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ export default Ember.Component.extend({
this.get('onChange')(path, value);
},

setAndBroadcastBool(path, trueVal, falseVal, value) {
let valueToSet = value === true ? trueVal : falseVal;
this.send('setAndBroadcast', path, valueToSet);
},

codemirrorUpdated(path, value, codemirror) {
codemirror.performLint();
const hasErrors = codemirror.state.lint.marked.length > 0;
Expand Down
10 changes: 10 additions & 0 deletions ui/app/controllers/vault.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Ember from 'ember';

export default Ember.Controller.extend({
queryParams: [
{
wrappedToken: 'wrapped_token',
},
],
wrappedToken: '',
});
10 changes: 4 additions & 6 deletions ui/app/controllers/vault/cluster/auth.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import Ember from 'ember';
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends';

export default Ember.Controller.extend({
queryParams: ['with'],
with: Ember.computed(function() {
return supportedAuthBackends()[0].type;
}),

vaultController: Ember.inject.controller('vault'),
queryParams: [{ authMethod: 'with' }],
wrappedToken: Ember.computed.alias('vaultController.wrappedToken'),
authMethod: '',
redirectTo: null,
});
4 changes: 2 additions & 2 deletions ui/app/helpers/nav-to-route.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ import Ember from 'ember';
const { Helper, inject } = Ember;

export default Helper.extend({
routing: inject.service('-routing'),
router: inject.service(),

compute([routeName, ...models], { replace = false }) {
return () => {
const router = this.get('routing.router');
const router = this.get('router');
const method = replace ? router.replaceWith : router.transitionTo;
return method.call(router, routeName, ...models);
};
Expand Down
17 changes: 14 additions & 3 deletions ui/app/models/auth-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ export default DS.Model.extend({
}),

tuneAttrs: computed(function() {
return expandAttributeMeta(this, ['description', 'config.{defaultLeaseTtl,maxLeaseTtl}']);
return expandAttributeMeta(this, [
'description',
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
]);
}),

//sys/mounts/auth/[auth-path]/tune.
Expand All @@ -61,12 +64,20 @@ export default DS.Model.extend({
'accessor',
'local',
'sealWrap',
'config.{defaultLeaseTtl,maxLeaseTtl}',
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
],

formFieldGroups: [
{ default: ['type', 'path'] },
{ 'Method Options': ['description', 'local', 'sealWrap', 'config.{defaultLeaseTtl,maxLeaseTtl}'] },
{
'Method Options': [
'description',
'config.listingVisibility',
'local',
'sealWrap',
'config.{defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
],
},
],

attrs: computed('formFields', function() {
Expand Down
21 changes: 21 additions & 0 deletions ui/app/models/mount-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,25 @@ export default Fragment.extend({
label: 'Max Lease TTL',
editType: 'ttl',
}),
auditNonHmacRequestKeys: attr({
label: 'Request keys excluded from HMACing in audit',
editType: 'stringArray',
helpText: "Keys that will not be HMAC'd by audit devices in the request data object.",
}),
auditNonHmacResponseKeys: attr({
label: 'Response keys excluded from HMACing in audit',
editType: 'stringArray',
helpText: "Keys that will not be HMAC'd by audit devices in the response data object.",
}),
listingVisibility: attr('string', {
editType: 'boolean',
label: 'List method when unauthenticated',
trueValue: 'unauth',
falseValue: 'hidden',
}),
passthroughRequestHeaders: attr({
label: 'Allowed passthrough request headers',
helpText: 'Headers to whitelist and pass from the request to the backend',
editType: 'stringArray',
}),
});
30 changes: 29 additions & 1 deletion ui/app/routes/vault/cluster/auth.js
Original file line number Diff line number Diff line change
@@ -1 +1,29 @@
export { default } from './cluster-route-base';
import ClusterRouteBase from './cluster-route-base';
import Ember from 'ember';

const { RSVP } = Ember;

export default ClusterRouteBase.extend({
beforeModel() {
return this.store.unloadAll('auth-method');
},
model() {
let cluster = this._super(...arguments);
return this.store
.findAll('auth-method', {
adapterOptions: {
unauthenticated: true,
},
})
.then(result => {
return RSVP.hash({
cluster,
methods: result,
});
});
},
resetController(controller) {
controller.set('wrappedToken', '');
controller.set('authMethod', '');
},
});
Loading

0 comments on commit 87d70fb

Please sign in to comment.