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 = `

Application Setup Wizard

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.

{{ app_name }} supports 3 different authentication schemas. The application you are setting up will need to support one of the following:

  • OAuth2
  • SAML
  • LDAP

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:

An app's identifier is how it is referenced in IAM configurations. This should preferrably be all lowercase, alphanumeric with underscores.
Okay, we'll help you set up {{ name }}. What type of authentication does it support?

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.

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.

Please provide the OAuth2 callback URL. This is where {{ app_name }} will redirect users after they have been authenticated.

{{ app_name }} only supports the authorization_code grant type.

We're going to register {{ name }} as a SAML2.0 service provider. This will allow it to interface with {{ app_name }}.

To do this, you need to provide {{ name }}'s entity ID, assertion consumer service URL, and single-logout URL (if supported).

Entity ID:
Assertion Consumer Service URL:
Single-Logout URL (optional):

Success! {{ name }} was added to {{ app_name }}'s records, and a SAML2.0 service provider was created.

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:

Configuring the Identity Provider

{{ app_name }} is the SAML2.0 identity provider in this case. To set it up, you'll need the following info:

  • Entity ID/Metadata: {{ make_url('/saml/metadata.xml') }}
  • Sign-On URL: {{ make_url('/saml/sso') }}
  • Single-Log-Out URL (if supported): {{ make_url('/saml/logout') }}

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.

Username:
Password:

Success! {{ name }} was added to {{ app_name }}'s records, and an LDAP client was created.

The next step is to configure {{ name }} to use {{ app_name }} to log on. Here's some information on getting it set up:

LDAP Credentials

If {{ name }} requires a bind user to query the LDAP server against, you can use these credentials:

  • Server address: {{ host }}
  • Server port: {{ ldap_config.port }}
  • Bind User DN: {{ ldap_config.login_field }}={{ ldap_username }},{{ ldap_config.authentication_base }},{{ ldap_config.base_dc }}
  • Password: the password you just set

User Searching

  • User search base: {{ ldap_config.authentication_base }},{{ ldap_config.base_dc }}
  • Group search base: {{ ldap_config.group_base }},{{ ldap_config.base_dc }}
  • Search filter: (&(objectClass=inetOrgPerson)(iamTarget={{ app.id }})({{ ldap_config.login_field }}=username_substituted_here))

Group Membership

Groups are made available in a manner compatible with OpenLDAP's memberOf overlay.

That means that groups are objectClass: groupOfNames and can be found in the memberOf attribute of the user object.

Groups have the form cn=group_name,{{ ldap_config.group_base }},{{ ldap_config.base_dc }}.

Other Considerations

{{ 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.

Here are a few settings to tweak:

  • User display name field: gecos (this is the full name of the user)
  • Paging chunk size: 0 (disable - {{ app_name }} does not support LDAP paging)
  • User e-mail field: mail
  • UUID attribute for users: {{ ldap_config.login_field }}
  • UUID attribute for groups: cn

Success! {{ name }} was added to {{ app_name }}'s records, and an OAuth2 client was created.

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:

User Authorization

First, redirect the user to {{ app_name }}. Configure {{ name }} to use this URL:

{{ make_url('/auth/service/oauth2/authorize') }}?client_id={{ oauth_client.uuid }}&redirect_uri={{ oauth_client.redirect_url }}

Once the user authenticates successfully, {{ app_name }} will redirect them back to {{ name }}.

Auth Code Redemption

Once the user is redirected back, {{ name }} will be given an authorization code which can be redeemed for a bearer token.

To redeem this code, {{ name }} should make a POST request to:

{{ make_url('/auth/service/oauth2/redeem') }}

It should have the following body fields:

  • code - the authorization code that was returned
  • client_id - {{ oauth_client.uuid }}
  • client_secret - {{ oauth_client.secret }}
  • grant_type - authorization_code

This will return an access_token that can be used to fetch user information from the {{ app_name }} API.

Fetching User Info

Once the auth code has been redeemed for a bearer token, that token can be used to make requests to the {{ app_name }} API.

Primarily, it can be used to fetch user information by making a GET request to the following URL:

{{ make_url('/api/v1/auth/users/me') }}

and including the bearer token in the headers like so: Authorization: Bearer AbCdEf124

Making Test Requests

To test out the API integration, you can generate API tokens for {{ name }}. You can do that by clicking on the User Menu > API Tokens.

` export default class AppSetupComponent extends Component { static get selector() { return 'coreid-app-setup' } static get template() { return template } static get props() { return [] } step = 0 btn_disabled = true btn_back = false btn_hidden = false btn_listing = false name = '' identifier = '' type = '' // ldap | saml | oauth oauth_redirect_uri = '' saml_entity_id = '' saml_acs_url = '' saml_slo_url = '' ldap_username = '' ldap_password = '' ldap_password_confirm = '' ldap_config = {} error_message = '' app = {} oauth_client = {} saml_provider = {} ldap_client = {} app_name = '' 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() } }