Skip to content

Commit

Permalink
Merge pull request #4787 from FlowFuse/test-ldap-connection
Browse files Browse the repository at this point in the history
Add connection test button to LDAP SSO page
  • Loading branch information
knolleary authored Nov 19, 2024
2 parents 38cba84 + d8df913 commit c98eaeb
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
61 changes: 61 additions & 0 deletions forge/ee/routes/sso/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const fp = require('fastify-plugin')

const { Client, InvalidCredentialsError } = require('ldapts')

module.exports = fp(async function (app, opts) {
// Get all
app.get('/ee/sso/providers', {
Expand Down Expand Up @@ -86,6 +88,65 @@ module.exports = fp(async function (app, opts) {
}
})

/**
* Test SSO creds, LDAP only at first
*/
app.post('/ee/sso/providers/test', {
preHandler: app.needsPermission('saml-provider:edit')
}, async (request, reply) => {
let url = request.body.options.server
if (!/^ldaps?:\/\//.test(url)) {
if (request.body.options.tls) {
url = 'ldaps://' + url
} else {
url = 'ldap://' + url
}
}

const clientOptions = { url }
if (request.body.options.tls) {
if (!request.body.options.tlsVerifyServer) {
clientOptions.tlsOptions = {
rejectUnauthorized: false
}
}
}

if (request.body.options.password === '__PLACEHOLDER__' && request.body.id) {
const provider = await app.db.models.SAMLProvider.byId(request.body.id)
request.body.options.password = provider.getOptions().password
}

let adminClient
try {
adminClient = new Client(clientOptions)
await adminClient.bind(request.body.options.username, request.body.options.password)
reply.send({})
} catch (err) {
const response = {}
if (err instanceof InvalidCredentialsError) {
response.code = 'incorrect_credentials'
response.error = 'Incorrect Credentials'
} else if (err.code === 'ECONNREFUSED') {
response.code = 'connection_refused'
response.error = 'Connection Refused'
} else if (err.code === 'ERR_INVALID_URL') {
response.code = 'invalid_url'
response.error = `${err.input} not valid LDAP URL`
} else {
response.code = 'unexpected_error'
response.error = err.toString()
}
reply.code(400).send(response)
} finally {
try {
if (adminClient) {
await adminClient.unbind()
}
} catch (err) {}
}
})

// IMPORTANT: register the auth routes last so that none of their internal
// handling get applied to the routes registered in this file.
await app.register(require('./auth'))
Expand Down
8 changes: 7 additions & 1 deletion frontend/src/api/sso.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,16 @@ const updateProvider = async (id, options) => {
const deleteProvider = async (id) => {
return await client.delete(`/ee/sso/providers/${id}`)
}
const testProvider = async (id, options) => {
return client.post('/ee/sso/providers/test', { id, ...options }).then(res => {
return res.data
})
}
export default {
createProvider,
updateProvider,
getProvider,
deleteProvider,
getProviders
getProviders,
testProvider
}
26 changes: 26 additions & 0 deletions frontend/src/pages/admin/Settings/SSO/createEditProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
Password
<template #description>The password to access the server</template>
</FormRow>
<ff-button :disabled="!allowTest" size="small" kind="secondary" @click="testProvider()">
Test Connection
</ff-button>
<FormRow v-model="input.options.baseDN">
Base DN
<template #description>The name of the base object to search for users</template>
Expand Down Expand Up @@ -134,6 +137,7 @@ import { mapState } from 'vuex'
import ssoApi from '../../../../api/sso.js'
import FormHeading from '../../../../components/FormHeading.vue'
import FormRow from '../../../../components/FormRow.vue'
import Alerts from '../../../../services/alerts.js'
export default {
name: 'AdminEditSSOProvider',
Expand Down Expand Up @@ -208,6 +212,9 @@ export default {
} else {
return `Edit SSO ${this.input.type.toUpperCase()} Configuration`
}
},
allowTest () {
return this.input.options.server && this.input.options.username && this.input.options.password
}
},
async beforeMount () {
Expand Down Expand Up @@ -337,6 +344,25 @@ export default {
}
}
this.originalValues = JSON.stringify(this.input)
},
async testProvider () {
const opts = {
...this.input
}
if (opts.type === 'ldap') {
if (!opts.options.tls) {
delete opts.options.tls
delete opts.options.tlsVerifyServer
}
try {
await ssoApi.testProvider(this.provider.id, opts)
Alerts.emit('Connection succeeded', 'confirmation')
} catch (err) {
const message = err.response.data.error
Alerts.emit(`Connection failed: ${message}`, 'warning')
}
}
}
}
Expand Down

0 comments on commit c98eaeb

Please sign in to comment.