import { Component } from '../../lib/vues6/vues6.js'
import { resource _service } from '../service/Resource.service.js'
import { location _service } from '../service/Location.service.js'
import { session } from '../service/Session.service.js'
const template = `
< div class = "card col-12 col-sm-10 offset-sm-1 col-md-8 offset-md-2 col-xl-6 offset-xl-3 mb-5" >
< div class = "card-title m-3 mt-4" > < h3 > Application Setup Wizard < / h 3 > < / d i v >
< div class = "card-body" >
< div v - if = "step === 0" >
< p > This wizard will walk you through setting up a new application to integrate with { { app _name } } . This will allow you to grant { { app _name } } users access to this application . < / p >
< p > { { app _name } } supports 3 different authentication schemas . The application you are setting up will need to support one of the following : < / p >
< ul >
< li > OAuth2 < / l i >
< li > SAML < / l i >
< li > LDAP < / l i >
< / u l >
< p > If the application supports any of these , it can be integrated with { { app _name } } to provide single - sign - on . All of these methods support { { app _name } } ' s IAM policy , but OAuth2 and SAML2 . 0 are preferred , because they support a web - based login flow . To get started , enter the application name and identifier : < / p >
< input
type = "text"
class = "form-control"
v - model = "name"
placeholder = "Awesome External App"
@ keyup = "on_key_up"
autofocus
>
< input
id = "app_id"
type = "text"
class = "form-control mt-3 mb-3"
v - model = "identifier"
placeholder = "awesome_external_app"
@ keyup = "on_key_up"
>
< small class = "text-secondary pad-top" > An app ' s identifier is how it is referenced in IAM configurations . This should preferrably be all lowercase , alphanumeric with underscores . < / s m a l l >
< / d i v >
< div v - if = "step === 1" >
Okay , we ' ll help you set up { { name } } . What type of authentication does it support ?
< button class = "btn btn-block btn-outline-success mt-4 mb-3" @ click = "on_type_click('oauth')" >
OAuth2
< br > < small > Less common , but best integration . Web - based login flow . < / s m a l l >
< / b u t t o n >
< button class = "btn btn-block btn-outline-primary mb-3" @ click = "on_type_click('saml')" >
SAML2 . 0
< br > < small > More common in enterprise applications . Web - based login flow . < / s m a l l >
< / b u t t o n >
< button class = "btn btn-block btn-outline-secondary" @ click = "on_type_click('ldap')" >
LDAP ( BindDN or Simple )
< br > < small > Most common in self - hosted applications . No web - based login flow . < / s m a l l >
< / b u t t o n >
< / d i v >
< div v - if = "step === 2 && type === 'oauth'" >
< p > We 're going to create an OAuth2 client for {{ name }}. This client will have the credentials that {{ name }} will use to authenticate users against {{ app_name }}' s API . < / p >
< p > By default , the OAuth2 client will be able to fetch information about individual users and groups . You can adjust this in the future by navigating to the OAuth2 Clients page . < / p >
< p > Please provide the OAuth2 callback URL . This is where { { app _name } } will redirect users after they have been authenticated . < / p >
< input
type = "text"
class = "form-control mt-3 mb-3"
v - model = "oauth_redirect_uri"
placeholder = "https://awesome.app/oauth2/callback"
@ keyup = "on_key_up"
>
< p > { { app _name } } only supports the < code > authorization _code < / c o d e > g r a n t t y p e . < / p >
< / d i v >
< div
v - if = "step === 2 && type === 'saml'"
>
< p > We ' re going to register { { name } } as a SAML2 . 0 service provider . This will allow it to interface with { { app _name } } . < / p >
< p > To do this , you need to provide { { name } } ' s entity ID , assertion consumer service URL , and single - logout URL ( if supported ) . < / p >
< span > Entity ID : < / s p a n >
< input
type = "text"
class = "form-control"
v - model = "saml_entity_id"
placeholder = "https://awesome.app/saml/metadata.xml"
@ keyup = "on_key_up"
>
< br >
< span > Assertion Consumer Service URL : < / s p a n >
< input
type = "text"
class = "form-control"
v - model = "saml_acs_url"
placeholder = "https://awesome.app/saml/acs"
@ keyup = "on_key_up"
>
< br >
< span > Single - Logout URL ( optional ) : < / s p a n >
< input
type = "text"
class = "form-control"
v - model = "saml_slo_url"
placeholder = "https://awesome.app/saml/logout"
@ keyup = "on_key_up"
>
< / d i v >
< div v - if = "step === 3 && type === 'saml'" >
< p > Success ! { { name } } was added to { { app _name } } ' s records , and a SAML2 . 0 service provider was created . < / p >
< p > The next step is to configure { { name } } to redirect users to { { app _name } } to log on . Here ' s some information on getting it set up : < / p >
< h4 > Configuring the Identity Provider < / h 4 >
< p > { { app _name } } is the SAML2 . 0 identity provider in this case . To set it up , you ' ll need the following info : < / p >
< ul >
< li > Entity ID / Metadata : < code > { { make _url ( '/saml/metadata.xml' ) } } < / c o d e > < / l i >
< li > Sign - On URL : < code > { { make _url ( '/saml/sso' ) } } < / c o d e > < / l i >
< li > Single - Log - Out URL ( if supported ) : < code > { { make _url ( '/saml/logout' ) } } < / c o d e > < / l i >
< / u l >
< / d i v >
< div v - if = "step === 2 && type === 'ldap'" >
< p > We 're going to register {{ name }} as an LDAP auth client. To do this, you' ll need to specify an LDAP username and password that { { name } } will use to authenticate users . < / p >
< span > Username : < / s p a n >
< input
type = "text"
v - model = "ldap_username"
class = 'form-control'
placeholder = "awesome_app_ldap"
@ keyup = "on_key_up"
>
< br >
< span > Password : < / s p a n >
< input
type = "password"
v - model = "ldap_password"
class = 'form-control'
placeholder = "Choose a password."
@ keyup = "on_key_up"
>
< input
type = "password"
v - model = "ldap_password_confirm"
class = 'form-control'
placeholder = "Confirm password."
@ keyup = "on_key_up"
>
< / d i v >
< div v - if = "step === 3 && type === 'ldap'" >
< p > Success ! { { name } } was added to { { app _name } } ' s records , and an LDAP client was created . < / p >
< p > The next step is to configure { { name } } to use { { app _name } } to log on . Here ' s some information on getting it set up : < / p >
< h4 > LDAP Credentials < / h 4 >
< p > If { { name } } requires a bind user to query the LDAP server against , you can use these credentials : < / p >
< ul >
< li > Server address : < code > { { host } } < / c o d e > < / l i >
< li > Server port : { { ldap _config . port } } < / l i >
< li > Bind User DN : < code > { { ldap _config . login _field } } = { { ldap _username } } , { { ldap _config . authentication _base } } , { { ldap _config . base _dc } } < / c o d e > < / l i >
< li > Password : < i > the password you just set < / i > < / l i >
< / u l >
< h4 > User Searching < / h 4 >
< ul >
< li > User search base : < code > { { ldap _config . authentication _base } } , { { ldap _config . base _dc } } < / c o d e > < / l i >
< li > Group search base : < code > { { ldap _config . group _base } } , { { ldap _config . base _dc } } < / c o d e > < / l i >
< li > Search filter : < code > ( & ( objectClass = inetOrgPerson ) ( iamTarget = { { app . id } } ) ( { { ldap _config . login _field } } = username _substituted _here ) ) < / c o d e > < / l i >
< / u l >
< h4 > Group Membership < / h 4 >
< p > Groups are made available in a manner compatible with OpenLDAP ' s memberOf overlay . < / p >
< p > That means that groups are < code > objectClass : groupOfNames < / c o d e > a n d c a n b e f o u n d i n t h e < c o d e > m e m b e r O f < / c o d e > a t t r i b u t e o f t h e u s e r o b j e c t . < / p >
< p > Groups have the form < code > cn = group _name , { { ldap _config . group _base } } , { { ldap _config . base _dc } } < / c o d e > . < / p >
< h4 > Other Considerations < / h 4 >
< p > { { app _name } } ' s built - in LDAP server provides the minimum - viable level of functionality required to authenticate users . That means it sometimes lacks features that more sophisticated LDAP clients expect . < / p >
< p > Here are a few settings to tweak : < / p >
< ul >
< li > User display name field : < code > gecos < / c o d e > ( t h i s i s t h e f u l l n a m e o f t h e u s e r ) < / l i >
< li > Paging chunk size : 0 ( disable - { { app _name } } does not support LDAP paging ) < / l i >
< li > User e - mail field : < code > mail < / c o d e > < / l i >
< li > UUID attribute for users : < code > { { ldap _config . login _field } } < / c o d e > < / l i >
< li > UUID attribute for groups : < code > cn < / c o d e > < / l i >
< / u l >
< / d i v >
< div v - if = "step === 3 && type === 'oauth'" >
< p > Success ! { { name } } was added to { { app _name } } ' s records , and an OAuth2 client was created . < / p >
< p > The next step is to configure { { name } } to redirect users to { { app _name } } to log on . Here ' s some information on getting it set up : < / p >
< h4 > User Authorization < / h 4 >
< p > First , redirect the user to { { app _name } } . Configure { { name } } to use this URL : < / p >
< code >
{ { make _url ( '/auth/service/oauth2/authorize' ) } } ? client _id = { { oauth _client . uuid } } & redirect _uri = { { oauth _client . redirect _url } }
< / c o d e >
< br > < br >
< p > Once the user authenticates successfully , { { app _name } } will redirect them back to { { name } } . < / p >
< h4 > Auth Code Redemption < / h 4 >
< p > Once the user is redirected back , { { name } } will be given an authorization code which can be redeemed for a bearer token . < / p >
< p > To redeem this code , { { name } } should make a POST request to : < / p >
< code >
{ { make _url ( '/auth/service/oauth2/redeem' ) } }
< / c o d e >
< br > < br >
< p > It should have the following body fields : < / p >
< ul >
< li > < code > code < / c o d e > - t h e a u t h o r i z a t i o n c o d e t h a t w a s r e t u r n e d < / l i >
< li > < code > client _id < / c o d e > - < c o d e > { { o a u t h _ c l i e n t . u u i d } } < / c o d e > < / l i >
< li > < code > client _secret < / c o d e > - < c o d e > { { o a u t h _ c l i e n t . s e c r e t } } < / c o d e > < / l i >
< li > < code > grant _type < / c o d e > - < c o d e > a u t h o r i z a t i o n _ c o d e < / c o d e > < / l i >
< / u l >
< p > This will return an < code > access _token < / c o d e > t h a t c a n b e u s e d t o f e t c h u s e r i n f o r m a t i o n f r o m t h e { { a p p _ n a m e } } A P I . < / p >
< h4 > Fetching User Info < / h 4 >
< p > Once the auth code has been redeemed for a bearer token , that token can be used to make requests to the { { app _name } } API . < / p >
< p > Primarily , it can be used to fetch user information by making a GET request to the following URL : < / p >
< code > { { make _url ( '/api/v1/auth/users/me' ) } } < / c o d e >
< br > < br >
< p > and including the bearer token in the headers like so : < code > Authorization : Bearer AbCdEf124 < / c o d e > < / p >
< h5 > Making Test Requests < / h 5 >
< p > To test out the API integration , you can generate API tokens for { { name } } . You can do that by clicking on the User Menu > < a href = "/dash/c/listing/reflect/Token" target = "_blank" > API Tokens < / a > . < / p >
< / d i v >
< / d i v >
< div class = "card-footer text-right" >
< span style = "color: darkred;" class = "font-italic mr-3" v - if = "error_message" > { { error _message } } < / s p a n >
< button
v - if = "btn_back"
class = "btn btn-outline-secondary"
@ click = "on_back_click"
> Back < / b u t t o n >
< button
v - if = "!btn_hidden"
class = "btn btn-outline-primary"
: disabled = "btn_disabled"
@ click = "on_next_click"
> Next < / b u t t o n >
< button
v - if = "btn_listing"
class = "btn btn-outline-success"
@ click = "on_listing_click"
> Finish < / b u t t o n >
< / d i v >
< / d i v >
`
export default class AppSetupComponent extends Component {
static get selector ( ) { return 'coreid-app-setup' }
static get template ( ) { return template }
static get props ( ) { return [ ] }
constructor ( ) {
super ( )
this . step = 0
this . btn _disabled = true
this . btn _back = false
this . btn _hidden = false
this . btn _listing = false
this . name = ''
this . identifier = ''
this . type = '' // ldap | saml | oauth
this . oauth _redirect _uri = ''
this . saml _entity _id = ''
this . saml _acs _url = ''
this . saml _slo _url = ''
this . ldap _username = ''
this . ldap _password = ''
this . ldap _password _confirm = ''
this . ldap _config = { }
this . error _message = ''
this . app = { }
this . oauth _client = { }
this . saml _provider = { }
this . ldap _client = { }
this . app _name = ''
this . host = ''
}
make _url ( path ) {
return session . url ( path )
}
async vue _on _create ( ) {
this . app _name = session . get ( 'app.name' )
this . host = session . host ( )
}
on _key _up ( event ) {
if ( this . step === 0 ) {
this . btn _disabled = ! this . name . trim ( ) || ! this . identifier . trim ( ) || ! this . identifier . match ( /^([a-z]|[A-Z]|[0-9]|_)+$/ )
} else if ( this . step === 2 && this . type === 'oauth' ) {
this . btn _disabled = ! this . oauth _redirect _uri . trim ( )
} else if ( this . step === 2 && this . type === 'saml' ) {
this . btn _disabled = ! this . saml _entity _id . trim ( ) || ! this . saml _acs _url . trim ( )
} else if ( this . step === 2 && this . type === 'ldap' ) {
this . btn _disabled = ! this . ldap _username . trim ( ) || ! this . ldap _username . match ( /^([a-z]|[A-Z]|[0-9]|_)+$/ ) || ! this . ldap _password || this . ldap _password !== this . ldap _password _confirm
}
if ( event . keyCode === 13 && ! this . btn _disabled ) {
// Enter was pressed
event . preventDefault ( )
event . stopPropagation ( )
return this . on _next _click ( )
}
}
async on _back _click ( ) {
this . step -= 1
if ( this . step === 0 ) this . btn _back = false
if ( this . step === 0 || this . step === 2 ) this . btn _hidden = false
}
async create _app ( merge _params = { } ) {
const params = {
... {
name : this . name ,
identifier : this . identifier ,
description : '' ,
} ,
... merge _params
}
const app _rsc = await resource _service . get ( 'App' )
return app _rsc . create ( params )
}
async on _next _click ( ) {
this . error _message = ''
try {
if ( this . step === 0 ) {
this . step += 1
this . btn _hidden = true
this . btn _back = true
} else if ( this . step === 2 && this . type === 'oauth' ) {
const client _rsc = await resource _service . get ( 'oauth/Client' )
const oauth _client _params = {
name : this . name ,
redirect _url : this . oauth _redirect _uri ,
api _scopes : [ 'v1:auth:users:get' , 'v1:auth:groups:get' ] ,
}
this . oauth _client = await client _rsc . create ( oauth _client _params )
this . app = await this . create _app ( {
oauth _client _ids : [ this . oauth _client . id ]
} )
} else if ( this . step === 2 && this . type === 'saml' ) {
const provider _rsc = await resource _service . get ( 'saml/Provider' )
const provider _params = {
name : this . name ,
acs _url : this . saml _acs _url ,
entity _id : this . saml _entity _id ,
}
if ( this . saml _slo _url )
provider _params . slo _url = this . saml _slo _url
this . saml _provider = await provider _rsc . create ( provider _params )
this . app = await this . create _app ( {
saml _service _provider _ids : [ this . saml _provider . id ]
} )
} else if ( this . step === 2 && this . type === 'ldap' ) {
const client _rsc = await resource _service . get ( 'ldap/Client' )
const client _params = {
name : this . name ,
uid : this . ldap _username ,
password : this . ldap _password ,
}
this . ldap _config = await client _rsc . server _config ( )
this . ldap _client = await client _rsc . create ( client _params )
this . app = await this . create _app ( {
ldap _client _ids : [ this . ldap _client . id ]
} )
}
if ( this . step === 2 ) {
this . step += 1
this . btn _disabled = true
this . btn _hidden = true
this . btn _listing = true
this . btn _back = false
}
} catch ( e ) {
if ( e . response && e . response . data && e . response . data . message )
this . error _message = e . response . data . message
else this . error _message = 'An unknown error occurred while saving the form.'
}
}
async on _type _click ( type ) {
this . type = type
this . step += 1
this . btn _hidden = false
this . btn _disabled = true
}
async on _listing _click ( ) {
await location _service . back ( )
}
}