Add basic LDAP bind functionality
This commit is contained in:
60
app/unit/LDAPControllerUnit.js
Normal file
60
app/unit/LDAPControllerUnit.js
Normal file
@@ -0,0 +1,60 @@
|
||||
const CanonicalUnit = require('libflitter/canon/CanonicalUnit')
|
||||
const LDAPController = require('../ldap/controllers/LDAPController')
|
||||
const StopError = require('libflitter/errors/StopError')
|
||||
|
||||
class LDAPControllerUnit extends CanonicalUnit {
|
||||
static get name() {
|
||||
return 'ldap_controllers'
|
||||
}
|
||||
|
||||
static get services() {
|
||||
return [...super.services, 'output']
|
||||
}
|
||||
|
||||
constructor(base_directory = './app/ldap/controllers') {
|
||||
super(base_directory)
|
||||
|
||||
this.canonical_item = 'ldap_controller'
|
||||
this.suffix = '.controller.js'
|
||||
}
|
||||
|
||||
async init_canonical_file({app, name, instance}) {
|
||||
if ( instance.prototype instanceof LDAPController ) {
|
||||
this.output.debug(`Registering LDAP controller: ${name}`)
|
||||
return new instance()
|
||||
} else {
|
||||
this.output.error(`LDAP Controller ${name} must extend base class LDAPController.`)
|
||||
throw new StopError(`LDAP Controller ${name} must extend base class LDAPController.`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve an unqualified canonical name to a registered canonical controller or method.
|
||||
* @param {string} name
|
||||
// * @returns {module:libflitter/controller/Controller~Controller|function}
|
||||
*/
|
||||
get(name) {
|
||||
const name_parts = name.split('.')
|
||||
const controller_instance = this.canonical_items[name_parts[0]]
|
||||
|
||||
if ( name_parts.length > 1 ) {
|
||||
const method_instance = controller_instance[name_parts[1]].bind(controller_instance)
|
||||
|
||||
if ( name_parts > 2 ) {
|
||||
let descending_value = method_instance
|
||||
name_parts.slice(2).forEach(part => {
|
||||
descending_value = descending_value[part]
|
||||
})
|
||||
|
||||
return descending_value
|
||||
} else {
|
||||
return method_instance
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is a bug in libflitter too!
|
||||
return controller_instance
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = LDAPControllerUnit
|
||||
41
app/unit/LDAPMiddlewareUnit.js
Normal file
41
app/unit/LDAPMiddlewareUnit.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const CanonicalUnit = require('libflitter/canon/CanonicalUnit')
|
||||
const LDAPMiddleware = require('../ldap/middleware/LDAPMiddleware')
|
||||
const StopError = require('libflitter/errors/StopError')
|
||||
|
||||
class LDAPMiddlewareUnit extends CanonicalUnit {
|
||||
static get services() {
|
||||
return [...super.services, 'output']
|
||||
}
|
||||
|
||||
static get name() {
|
||||
return 'ldap_middleware'
|
||||
}
|
||||
|
||||
constructor(base_directory = './app/ldap/middleware') {
|
||||
super(base_directory)
|
||||
|
||||
this.canonical_item = 'ldap_middleware'
|
||||
this.suffix = '.middleware.js'
|
||||
}
|
||||
|
||||
async init_canonical_file({app, name, instance}) {
|
||||
if ( instance.prototype instanceof LDAPMiddleware ) {
|
||||
this.output.debug(`Registering LDAP middleware: ${name}`)
|
||||
return new instance()
|
||||
} else {
|
||||
this.output.error(`LDAP middleware class ${name} must be an instance of LDAPMiddleware.`)
|
||||
throw new StopError(`LDAP middleware class ${name} must be an instance of LDAPMiddleware.`)
|
||||
}
|
||||
}
|
||||
|
||||
get(name) {
|
||||
const item = super.get(name)
|
||||
if ( item instanceof LDAPMiddleware ) {
|
||||
return item.test.bind(item)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = LDAPMiddlewareUnit
|
||||
@@ -1,21 +0,0 @@
|
||||
const Unit = require('libflitter/Unit')
|
||||
|
||||
class LDAPRegistry extends Unit {
|
||||
static get name() {
|
||||
return 'ldap_registry'
|
||||
}
|
||||
|
||||
static get services() {
|
||||
return [...super.services, 'output']
|
||||
}
|
||||
|
||||
async go(app) {
|
||||
|
||||
}
|
||||
|
||||
async cleanup(app) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = LDAPRegistry
|
||||
101
app/unit/LDAPRoutingUnit.js
Normal file
101
app/unit/LDAPRoutingUnit.js
Normal file
@@ -0,0 +1,101 @@
|
||||
const CanonicalUnit = require('libflitter/canon/CanonicalUnit')
|
||||
|
||||
class LDAPRoutingUnit extends CanonicalUnit {
|
||||
static get name() {
|
||||
return 'ldap_routers'
|
||||
}
|
||||
|
||||
static get services() {
|
||||
return [...super.services, 'output', 'canon', 'ldap_server']
|
||||
}
|
||||
|
||||
constructor(base_directory = './app/ldap/routes') {
|
||||
super(base_directory)
|
||||
|
||||
this.canonical_item = 'ldap_router'
|
||||
this.suffix = '.routes.js'
|
||||
}
|
||||
|
||||
async init_canonical_file({app, name, instance}) {
|
||||
const router_middleware = []
|
||||
if ( !instance ) {
|
||||
this.output.warn(`Skipping LDAP routing file ${name}. No members were exported.`)
|
||||
}
|
||||
|
||||
this.output.info(`Building LDAP routes for router ${name}.`)
|
||||
|
||||
// Load the router-level middleware functions
|
||||
if ( Array.isArray(instance.middleware) ) {
|
||||
for ( const mw of instance.middleware ) {
|
||||
const mw_instance = this.canon.get(`ldap_middleware::${mw}`)
|
||||
if ( !mw_instance ) {
|
||||
const msg = `Unable to create LDAP routes. Invalid or unknown LDAP middleware: ldap_middleware::${mw} in router ${name}.`
|
||||
this.output.error(msg)
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
router_middleware.push(mw_instance)
|
||||
}
|
||||
}
|
||||
|
||||
this.output.debug(`Found ${router_middleware.length} router-level middlewares.`)
|
||||
|
||||
// Determine the prefix (suffix) and total suffix from config
|
||||
let suffix = []
|
||||
if ( instance.prefix && typeof instance.prefix === 'string' ) {
|
||||
suffix.push(instance.prefix)
|
||||
} else if ( instance.prefix !== false ) {
|
||||
this.output.warn(`No prefix specified for LDAP routing file ${name}.`)
|
||||
}
|
||||
|
||||
// If the server has a base DC=...,DC=... &c. suffix, include that
|
||||
if ( this.ldap_server.config.schema.base_dc ) {
|
||||
suffix.push(this.ldap_server.config.schema.base_dc)
|
||||
}
|
||||
|
||||
suffix = suffix.join(',')
|
||||
|
||||
// Load the individual routes
|
||||
const supported_ldap_types = [
|
||||
'search', 'bind', 'add', 'del', 'modify', 'compare', 'modifyDN', 'exop', 'unbind',
|
||||
]
|
||||
|
||||
// Iterate over the various query types that might be in the definition
|
||||
for ( const type of supported_ldap_types ) {
|
||||
if ( typeof instance[type] === 'object' ) {
|
||||
// Iterate over each of the route definitions in the type definition
|
||||
for ( const route_prefix in instance[type] ) {
|
||||
if ( !instance[type].hasOwnProperty(route_prefix) ) continue
|
||||
let route_handlers = instance[type][route_prefix]
|
||||
if ( typeof route_handlers === 'string' ) route_handlers = [route_handlers]
|
||||
|
||||
if ( !Array.isArray(route_handlers) ) {
|
||||
const msg = `Invalid route handlers for route ${route_prefix} (${type}) in router ${name}.`
|
||||
this.output.error(msg)
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
let route_functions = [...router_middleware]
|
||||
// For each of the route handler definitions, resolve the canonical
|
||||
for ( const route_handler_name of route_handlers ) {
|
||||
const route_handler = this.canon.get(route_handler_name)
|
||||
if ( !route_handler || typeof route_handler !== 'function' ) {
|
||||
const msg = `Unable to resolve route handler for route ${route_prefix} (${type}) in router ${name}. Handler name: ${route_handler_name}`
|
||||
this.output.error(msg)
|
||||
throw new Error(msg)
|
||||
}
|
||||
|
||||
route_functions.push(route_handler)
|
||||
}
|
||||
|
||||
this.output.debug(`Registering route ${type} :: ${[route_prefix, suffix].join(',')} with ${route_functions.length} handlers.`)
|
||||
this.ldap_server.server[type]([route_prefix, suffix].join(','), ...route_functions)
|
||||
}
|
||||
} else {
|
||||
this.output.warn(`Missing or invalid LDAP protocol definition ${type} in router ${name}. The protocol will be skipped.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = LDAPRoutingUnit
|
||||
@@ -10,6 +10,20 @@ class LDAPServerUnit extends Unit {
|
||||
return [...super.services, 'configs', 'express', 'output']
|
||||
}
|
||||
|
||||
auth_dn() {
|
||||
return this.build_dn(this.config.schema.authentication_base)
|
||||
}
|
||||
|
||||
anonymous() {
|
||||
return LDAP.parseDN('cn=anonymous')
|
||||
}
|
||||
|
||||
build_dn(...parts) {
|
||||
parts = parts.flat()
|
||||
parts.push(this.config.schema.base_dc)
|
||||
return LDAP.parseDN(parts.join(','))
|
||||
}
|
||||
|
||||
async go(app) {
|
||||
this.config = this.configs.get('ldap:server')
|
||||
const server_config = {}
|
||||
@@ -17,7 +31,7 @@ class LDAPServerUnit extends Unit {
|
||||
// If Flitter is configured to use an SSL certificate,
|
||||
// use it to enable LDAPS in the server.
|
||||
if ( this.express.use_ssl() ) {
|
||||
this.output.info('[LDAP Server] Using configured SSL certificate to enable LDAPS.')
|
||||
this.output.info('Using configured SSL certificate. The LDAP server will require an ldaps:// connection.')
|
||||
server_config.certificate = await this.express.ssl_certificate()
|
||||
server_config.key = await this.express.ssl_key()
|
||||
}
|
||||
@@ -28,9 +42,10 @@ class LDAPServerUnit extends Unit {
|
||||
this.server.maxConnections = this.config.max_connections
|
||||
}
|
||||
|
||||
this.output.info(`[LDAP Server] Will listen on ${this.config.interface}:${this.config.port}`)
|
||||
this.output.info(`Will listen on ${this.config.interface}:${this.config.port}`)
|
||||
await new Promise((res, rej) => {
|
||||
this.server.listen(this.config.port, this.config.interface, () => {
|
||||
this.output.success(`LDAP server listening on port ${this.config.port}...`)
|
||||
res()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user