2019-02-14 15:39:19 +00:00
import { next } from '@ember/runloop' ;
2018-09-25 16:28:26 +00:00
import { inject as service } from '@ember/service' ;
import { match , alias , or } from '@ember/object/computed' ;
import { assign } from '@ember/polyfills' ;
import { dasherize } from '@ember/string' ;
import Component from '@ember/component' ;
import { get , computed } from '@ember/object' ;
2018-04-03 14:16:57 +00:00
import { supportedAuthBackends } from 'vault/helpers/supported-auth-backends' ;
2018-07-05 18:28:12 +00:00
import { task } from 'ember-concurrency' ;
2018-04-03 14:16:57 +00:00
const BACKENDS = supportedAuthBackends ( ) ;
2018-04-17 22:04:34 +00:00
2019-04-03 21:06:20 +00:00
/ * *
* @ module AuthForm
* The ` AuthForm ` is used to sign users into Vault .
*
* @ example ` ` ` js
* // All properties are passed in via query params.
* < AuthForm @ wrappedToken = { { wrappedToken } } @ cluster = { { model } } @ namespace = { { namespaceQueryParam } } @ redirectTo = { { redirectTo } } @ selectedAuth = { { authMethod } } / > ` ` `
*
* @ param wrappedToken = null { String } - The auth method that is currently selected in the dropdown .
* @ param cluster = null { Object } - The auth method that is currently selected in the dropdown . This corresponds to an Ember Model .
* @ param namespace = null { String } - The currently active namespace .
* @ param redirectTo = null { String } - The name of the route to redirect to .
* @ param selectedAuth = null { String } - The auth method that is currently selected in the dropdown .
* /
2018-04-17 22:04:34 +00:00
const DEFAULTS = {
token : null ,
username : null ,
password : null ,
2018-07-20 21:48:25 +00:00
customPath : null ,
2018-04-17 22:04:34 +00:00
} ;
2018-09-25 16:28:26 +00:00
export default Component . extend ( DEFAULTS , {
router : service ( ) ,
auth : service ( ) ,
flashMessages : service ( ) ,
store : service ( ) ,
csp : service ( 'csp-event' ) ,
2018-07-05 18:28:12 +00:00
2019-02-14 15:39:19 +00:00
// passed in via a query param
2018-07-05 18:28:12 +00:00
selectedAuth : null ,
methods : null ,
cluster : null ,
redirectTo : null ,
2018-08-16 17:48:24 +00:00
namespace : null ,
2018-09-25 16:28:26 +00:00
wrappedToken : null ,
2018-08-16 17:48:24 +00:00
// internal
oldNamespace : null ,
didReceiveAttrs ( ) {
this . _super ( ... arguments ) ;
let token = this . get ( 'wrappedToken' ) ;
let newMethod = this . get ( 'selectedAuth' ) ;
let oldMethod = this . get ( 'oldSelectedAuth' ) ;
let ns = this . get ( 'namespace' ) ;
let oldNS = this . get ( 'oldNamespace' ) ;
if ( oldNS === null || oldNS !== ns ) {
this . get ( 'fetchMethods' ) . perform ( ) ;
}
this . set ( 'oldNamespace' , ns ) ;
if ( oldMethod && oldMethod !== newMethod ) {
this . resetDefaults ( ) ;
}
this . set ( 'oldSelectedAuth' , newMethod ) ;
if ( token ) {
this . get ( 'unwrapToken' ) . perform ( token ) ;
}
} ,
2018-07-05 18:28:12 +00:00
2018-04-03 14:16:57 +00:00
didRender ( ) {
2018-07-05 18:28:12 +00:00
this . _super ( ... arguments ) ;
2018-09-25 16:28:26 +00:00
let firstMethod = this . firstMethod ( ) ;
2018-04-03 14:16:57 +00:00
// on very narrow viewports the active tab may be overflowed, so we scroll it into view here
2018-07-05 18:28:12 +00:00
let activeEle = this . element . querySelector ( 'li.is-active' ) ;
if ( activeEle ) {
activeEle . scrollIntoView ( ) ;
}
2018-09-25 16:28:26 +00:00
// set `with` to the first method
2018-09-05 19:28:10 +00:00
if (
2018-09-25 16:28:26 +00:00
( this . get ( 'fetchMethods.isIdle' ) && firstMethod && ! this . get ( 'selectedAuth' ) ) ||
2018-09-05 19:28:10 +00:00
( this . get ( 'selectedAuth' ) && ! this . get ( 'selectedAuthBackend' ) )
) {
2018-09-25 16:28:26 +00:00
this . set ( 'selectedAuth' , firstMethod ) ;
2018-07-05 18:28:12 +00:00
}
} ,
firstMethod ( ) {
let firstMethod = this . get ( 'methodsToShow.firstObject' ) ;
2018-09-25 16:28:26 +00:00
if ( ! firstMethod ) return ;
2018-07-05 18:28:12 +00:00
// prefer backends with a path over those with a type
return get ( firstMethod , 'path' ) || get ( firstMethod , 'type' ) ;
2018-04-03 14:16:57 +00:00
} ,
2018-04-17 22:04:34 +00:00
resetDefaults ( ) {
this . setProperties ( DEFAULTS ) ;
} ,
2018-09-25 16:28:26 +00:00
selectedAuthIsPath : match ( 'selectedAuth' , /\/$/ ) ,
selectedAuthBackend : computed ( 'methods' , 'methods.[]' , 'selectedAuth' , 'selectedAuthIsPath' , function ( ) {
let methods = this . get ( 'methods' ) ;
let selectedAuth = this . get ( 'selectedAuth' ) ;
let keyIsPath = this . get ( 'selectedAuthIsPath' ) ;
if ( ! methods ) {
return { } ;
2018-07-05 18:28:12 +00:00
}
2018-09-25 16:28:26 +00:00
if ( keyIsPath ) {
return methods . findBy ( 'path' , selectedAuth ) ;
}
return BACKENDS . findBy ( 'type' , selectedAuth ) ;
} ) ,
2018-04-03 14:16:57 +00:00
2018-07-05 18:28:12 +00:00
providerPartialName : computed ( 'selectedAuthBackend' , function ( ) {
let type = this . get ( 'selectedAuthBackend.type' ) || 'token' ;
type = type . toLowerCase ( ) ;
2018-09-25 16:28:26 +00:00
let templateName = dasherize ( type ) ;
2018-07-05 18:28:12 +00:00
return ` partials/auth-form/ ${ templateName } ` ;
2018-04-03 14:16:57 +00:00
} ) ,
2018-09-25 16:28:26 +00:00
hasCSPError : alias ( 'csp.connectionViolations.firstObject' ) ,
2018-04-05 21:36:33 +00:00
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. ` ,
2018-07-05 18:28:12 +00:00
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' ) ;
} ) ,
2018-08-16 17:48:24 +00:00
methodsToShow : computed ( 'methods' , function ( ) {
2018-07-05 18:28:12 +00:00
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 ) {
2019-03-29 23:40:12 +00:00
// will be using the Token Auth Method, so set it here
2018-07-05 18:28:12 +00:00
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 ) ;
2019-02-14 15:39:19 +00:00
next ( ( ) => {
this . send ( 'doSubmit' ) ;
} ) ;
2018-07-05 18:28:12 +00:00
} catch ( e ) {
this . set ( 'error' , ` Token unwrap failed: ${ e . errors [ 0 ] } ` ) ;
}
2019-04-10 14:36:32 +00:00
} ) . withTestWaiter ( ) ,
2018-07-05 18:28:12 +00:00
2018-08-16 17:48:24 +00:00
fetchMethods : task ( function * ( ) {
let store = this . get ( 'store' ) ;
try {
let methods = yield store . findAll ( 'auth-method' , {
adapterOptions : {
unauthenticated : true ,
} ,
} ) ;
2018-10-02 15:05:34 +00:00
this . set ( 'methods' , methods . map ( m => m . serialize ( { includeId : true } ) ) ) ;
2019-02-14 15:39:19 +00:00
next ( ( ) => {
2018-10-02 15:05:34 +00:00
store . unloadAll ( 'auth-method' ) ;
} ) ;
2018-08-16 17:48:24 +00:00
} catch ( e ) {
2019-03-29 23:40:12 +00:00
this . set ( 'error' , ` There was an error fetching Auth Methods: ${ e . errors [ 0 ] } ` ) ;
2018-08-16 17:48:24 +00:00
}
2019-04-10 14:36:32 +00:00
} ) . withTestWaiter ( ) ,
2018-08-16 17:48:24 +00:00
2019-02-14 15:39:19 +00:00
showLoading : or ( 'isLoading' , 'authenticate.isRunning' , 'fetchMethods.isRunning' , 'unwrapToken.isRunning' ) ,
2018-08-16 17:48:24 +00:00
2019-04-09 17:42:51 +00:00
handleError ( e , prefixMessage = true ) {
2018-04-03 14:16:57 +00:00
this . set ( 'loading' , false ) ;
2019-01-18 22:04:40 +00:00
if ( ! e . errors ) {
return e ;
}
2018-04-05 21:36:33 +00:00
let errors = e . errors . map ( error => {
if ( error . detail ) {
return error . detail ;
}
return error ;
} ) ;
2019-04-09 17:42:51 +00:00
let message = prefixMessage ? 'Authentication failed: ' : '' ;
this . set ( 'error' , ` ${ message } ${ errors . join ( '.' ) } ` ) ;
2018-04-03 14:16:57 +00:00
} ,
2018-10-02 13:53:39 +00:00
authenticate : task ( function * ( backendType , data ) {
let clusterId = this . cluster . id ;
let targetRoute = this . redirectTo || 'vault.cluster' ;
try {
let authResponse = yield this . auth . authenticate ( { clusterId , backend : backendType , data } ) ;
let { isRoot , namespace } = authResponse ;
let transition = this . router . transitionTo ( targetRoute , { queryParams : { namespace } } ) ;
// returning this w/then because if we keep it
// in the task, it will get cancelled when the component in un-rendered
2019-05-03 03:20:28 +00:00
yield transition . followRedirects ( ) . then ( ( ) => {
2018-10-02 13:53:39 +00:00
if ( isRoot ) {
this . flashMessages . warning (
'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.'
) ;
}
} ) ;
} catch ( e ) {
this . handleError ( e ) ;
}
2019-04-10 14:36:32 +00:00
} ) . withTestWaiter ( ) ,
2018-10-02 13:53:39 +00:00
2018-04-03 14:16:57 +00:00
actions : {
2018-04-17 22:04:34 +00:00
doSubmit ( ) {
2019-02-14 15:39:19 +00:00
let passedData , e ;
if ( arguments . length > 1 ) {
[ passedData , e ] = arguments ;
} else {
[ e ] = arguments ;
}
if ( e ) {
e . preventDefault ( ) ;
}
2018-04-17 22:04:34 +00:00
let data = { } ;
2018-04-03 14:16:57 +00:00
this . setProperties ( {
error : null ,
} ) ;
2018-07-05 18:28:12 +00:00
let backend = this . get ( 'selectedAuthBackend' ) || { } ;
let backendMeta = BACKENDS . find (
2018-08-16 17:48:24 +00:00
b => ( get ( b , 'type' ) || '' ) . toLowerCase ( ) === ( get ( backend , 'type' ) || '' ) . toLowerCase ( )
2018-07-05 18:28:12 +00:00
) ;
2018-08-16 17:48:24 +00:00
let attributes = get ( backendMeta || { } , 'formAttributes' ) || { } ;
2018-04-17 22:04:34 +00:00
2018-09-25 16:28:26 +00:00
data = assign ( data , this . getProperties ( ... attributes ) ) ;
2019-02-14 15:39:19 +00:00
if ( passedData ) {
data = assign ( data , passedData ) ;
}
2018-07-20 21:48:25 +00:00
if ( this . get ( 'customPath' ) || get ( backend , 'id' ) ) {
data . path = this . get ( 'customPath' ) || get ( backend , 'id' ) ;
2018-04-03 14:16:57 +00:00
}
2019-02-14 15:39:19 +00:00
return this . authenticate . unlinked ( ) . perform ( backend . type , data ) ;
2018-04-03 14:16:57 +00:00
} ,
2019-04-09 17:42:51 +00:00
handleError ( e ) {
if ( e ) {
this . handleError ( e , false ) ;
} else {
this . set ( 'error' , null ) ;
}
} ,
2018-04-03 14:16:57 +00:00
} ,
} ) ;