2020-04-17 00:59:48 +00:00
|
|
|
const Unit = require('libflitter/Unit')
|
|
|
|
const LDAP = require('ldapjs')
|
2020-04-22 14:19:25 +00:00
|
|
|
const Validator = require('email-validator')
|
2020-05-12 01:26:09 +00:00
|
|
|
const net = require('net')
|
2021-03-11 00:22:51 +00:00
|
|
|
const fs = require('fs')
|
2020-04-17 00:59:48 +00:00
|
|
|
|
2020-05-04 01:16:54 +00:00
|
|
|
// TODO support logging ALL ldap requests when in DEBUG, not just routed ones
|
|
|
|
// TODO need to support LDAP server auto-discovery/detection features
|
|
|
|
|
2020-04-17 00:59:48 +00:00
|
|
|
class LDAPServerUnit extends Unit {
|
|
|
|
static get name() {
|
|
|
|
return 'ldap_server'
|
|
|
|
}
|
|
|
|
|
|
|
|
static get services() {
|
|
|
|
return [...super.services, 'configs', 'express', 'output']
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:46:19 +00:00
|
|
|
/**
|
|
|
|
* Get the standard format for LDAP DNs. Can be passed into
|
|
|
|
* ldapjs/DN.format().
|
|
|
|
* @returns {object}
|
|
|
|
*/
|
|
|
|
standard_format() {
|
2020-05-04 01:16:54 +00:00
|
|
|
return this.configs.get('ldap:server.format')
|
2020-04-21 03:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the LDAP.js DN for the user auth base.
|
|
|
|
* @returns {ldap/DN}
|
|
|
|
*/
|
2020-04-18 00:25:33 +00:00
|
|
|
auth_dn() {
|
|
|
|
return this.build_dn(this.config.schema.authentication_base)
|
|
|
|
}
|
|
|
|
|
2020-05-04 01:16:54 +00:00
|
|
|
group_dn() {
|
|
|
|
return this.build_dn(this.config.schema.group_base)
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:46:19 +00:00
|
|
|
/**
|
|
|
|
* Get the anonymous DN.
|
|
|
|
* @returns {ldap/DN}
|
|
|
|
*/
|
2020-04-18 00:25:33 +00:00
|
|
|
anonymous() {
|
|
|
|
return LDAP.parseDN('cn=anonymous')
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:46:19 +00:00
|
|
|
/**
|
|
|
|
* Returns true if the string is a valid e-mail address.
|
|
|
|
*
|
|
|
|
* @see https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
|
|
|
|
* @param {string} email
|
|
|
|
* @returns {boolean}
|
|
|
|
*/
|
|
|
|
validate_email(email) {
|
2020-04-22 14:19:25 +00:00
|
|
|
return Validator.validate(email)
|
2020-04-21 03:46:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Build an LDAP.js DN object from a set of string RDNs.
|
|
|
|
* @param {...string} parts
|
|
|
|
* @returns {ldap/DN}
|
|
|
|
*/
|
2020-04-18 00:25:33 +00:00
|
|
|
build_dn(...parts) {
|
|
|
|
parts = parts.flat()
|
|
|
|
parts.push(this.config.schema.base_dc)
|
|
|
|
return LDAP.parseDN(parts.join(','))
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:46:19 +00:00
|
|
|
/**
|
|
|
|
* Starts the LDAP server.
|
|
|
|
* @param {module:libflitter/app/FlitterApp~FlitterApp} app
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2020-04-17 00:59:48 +00:00
|
|
|
async go(app) {
|
|
|
|
this.config = this.configs.get('ldap:server')
|
|
|
|
const server_config = {}
|
|
|
|
|
|
|
|
// If Flitter is configured to use an SSL certificate,
|
|
|
|
// use it to enable LDAPS in the server.
|
2021-03-11 00:22:51 +00:00
|
|
|
if ( this.config.ssl?.enable ) {
|
|
|
|
this.output.info('Using configured SSL certificate. The LDAP server will require an ldaps:// connection.')
|
|
|
|
server_config.certificate = fs.readFileSync(this.config.ssl.certificate)
|
|
|
|
server_config.key = fs.readFileSync(this.config.ssl.key)
|
|
|
|
} else if ( this.express.use_ssl() ) {
|
2020-04-18 00:25:33 +00:00
|
|
|
this.output.info('Using configured SSL certificate. The LDAP server will require an ldaps:// connection.')
|
2020-04-17 00:59:48 +00:00
|
|
|
server_config.certificate = await this.express.ssl_certificate()
|
|
|
|
server_config.key = await this.express.ssl_key()
|
|
|
|
}
|
|
|
|
|
|
|
|
this.server = LDAP.createServer(server_config)
|
|
|
|
|
|
|
|
if ( this.config.max_connections ) {
|
|
|
|
this.server.maxConnections = this.config.max_connections
|
|
|
|
}
|
|
|
|
|
2020-04-18 00:25:33 +00:00
|
|
|
this.output.info(`Will listen on ${this.config.interface}:${this.config.port}`)
|
2020-05-12 01:26:09 +00:00
|
|
|
if ( await this.port_free() ) {
|
|
|
|
await new Promise((res, rej) => {
|
|
|
|
this.server.listen(this.config.port, this.config.interface, (err) => {
|
|
|
|
this.output.success(`LDAP server listening on port ${this.config.port}...`)
|
|
|
|
res()
|
|
|
|
})
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.output.error(`LDAP server port ${this.config.port} is not available. The LDAP server was not started.`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async port_free() {
|
|
|
|
return new Promise((res, rej) => {
|
|
|
|
const server = net.createServer()
|
2020-05-17 04:55:08 +00:00
|
|
|
server.once('error', (e) => {
|
|
|
|
res(false)
|
|
|
|
})
|
2020-05-12 01:26:09 +00:00
|
|
|
server.once('listening', () => {
|
|
|
|
server.close()
|
2020-05-17 04:55:08 +00:00
|
|
|
res(true)
|
2020-04-17 00:59:48 +00:00
|
|
|
})
|
2020-05-12 01:26:09 +00:00
|
|
|
server.listen(this.config.port)
|
2020-04-17 00:59:48 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-04-21 03:46:19 +00:00
|
|
|
/**
|
|
|
|
* Stops the LDAP server.
|
|
|
|
* @param {module:libflitter/app/FlitterApp~FlitterApp} app
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2020-04-17 00:59:48 +00:00
|
|
|
async cleanup(app) {
|
|
|
|
this.server.close()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = exports = LDAPServerUnit
|