114 lines
3.8 KiB
JavaScript
114 lines
3.8 KiB
JavaScript
const { Injectable } = require('flitter-di')
|
|
const { ImplementationError } = require('libflitter')
|
|
const LDAP = require('ldapjs')
|
|
|
|
class LDAPController extends Injectable {
|
|
resource_type = 'items'
|
|
|
|
// TODO make use of this better
|
|
check_attribute = 'ldap_visible' // or false
|
|
|
|
static get services() {
|
|
return [...super.services, 'ldap_server', 'configs']
|
|
}
|
|
|
|
async compare(req, res, next) {
|
|
if ( !req.user.can(`ldap:compare:${this.resource_type}`)) {
|
|
return next(new LDAP.InsufficientAccessRightsError())
|
|
}
|
|
|
|
// Make sure the resource exists
|
|
const item = await this.get_resource_from_dn(req.dn)
|
|
if ( !item ) {
|
|
return next(LDAP.NoSuchObjectError())
|
|
}
|
|
|
|
// Make sure it has the requested attribute
|
|
const value = (await item.to_ldap())[req.attribute]
|
|
if ( typeof value === 'undefined' ) {
|
|
return next(new LDAP.NoSuchAttributeError())
|
|
}
|
|
|
|
// Check if it matches the request value
|
|
const values = Array.isArray(value) ? value : [value]
|
|
const matches = values.some(x => x === req.value)
|
|
|
|
res.end(matches)
|
|
return next()
|
|
}
|
|
|
|
async bind(req, res, next) {
|
|
const auth_dn = this.ldap_server.auth_dn()
|
|
|
|
// Make sure the DN is valid
|
|
if ( !req.dn.childOf(auth_dn) ) {
|
|
this.output.debug(`Bind failure: ${req.dn} not in ${auth_dn}`)
|
|
return next(new LDAP.InvalidCredentialsError('Cannot bind to DN outsize of the authentication base.'))
|
|
}
|
|
|
|
// Get the item
|
|
const item = await this.get_resource_from_dn(req.dn)
|
|
if ( !item ) {
|
|
this.output.debug(`Bind failure: ${req.dn} not found`)
|
|
return next(new LDAP.NoSuchObjectError())
|
|
}
|
|
|
|
// If the object is can-able, make sure it can bind
|
|
if ( typeof item.can === 'function' && !item.can('ldap:bind') ) {
|
|
this.output.debug(`Bind failure: User not allowed to bind`)
|
|
return next(new LDAP.InsufficientAccessRightsError())
|
|
}
|
|
|
|
// Check if the credentials are valid
|
|
if ( !(await item.check_credential_string(req.credentials)) ) {
|
|
return next(new LDAP.InvalidCredentialsError())
|
|
}
|
|
|
|
// Check if the resource has a trap. If so, deny access.
|
|
if ( item.trap ) {
|
|
return next(new LDAP.InvalidCredentialsError('This resource currently has a login trap set. Please visit the web UI to release.'))
|
|
}
|
|
|
|
this.output.info(`Successfully bound resource as DN: ${req.dn.format(this.configs.get('ldap:server.format'))}.`)
|
|
res.end()
|
|
return next()
|
|
}
|
|
|
|
async delete(req, res, next) {
|
|
if ( !req.user.can(`ldap:delete:${this.resource_type}`) ) {
|
|
return next(new LDAP.InsufficientAccessRightsError())
|
|
}
|
|
|
|
// Get the base DN
|
|
const base_dn = await this.get_base_dn()
|
|
|
|
// Make sure it's a parent of the request DN
|
|
if ( !base_dn.parentOf(req.dn) ) {
|
|
this.output.warn(`Attempted to perform resource deletion on invalid DN: ${req.dn.format(this.configs.get('ldap:server.format'))}`)
|
|
return next(new LDAP.InsufficientAccessRightsError(`Target DN must be a member of the base DN: ${base_dn.format(this.configs.get('ldap:server.format'))}.`))
|
|
}
|
|
|
|
// Fetch the resource (error if not found)
|
|
const item = await this.get_resource_from_dn(req.dn)
|
|
if ( !item ) {
|
|
return next(new LDAP.NoSuchObjectError())
|
|
}
|
|
|
|
// Delete it - TODO full soft delete, or just ldap_visible = false?
|
|
await item.delete()
|
|
|
|
res.end()
|
|
return next()
|
|
}
|
|
|
|
async get_resource_from_dn(dn) {
|
|
throw new ImplementationError()
|
|
}
|
|
|
|
async get_base_dn() {
|
|
throw new ImplementationError()
|
|
}
|
|
}
|
|
|
|
module.exports = exports = LDAPController
|