16 changed files with 1980 additions and 223 deletions
@ -1,7 +1,105 @@ |
|||
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', 'ldap_dn_format'] |
|||
} |
|||
|
|||
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 = 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) ) { |
|||
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 ) { |
|||
return next(new LDAP.NoSuchObject()) |
|||
} |
|||
|
|||
// If the object is can-able, make sure it can bind
|
|||
if ( typeof item.can === 'function' && !item.can('ldap:bind') ) { |
|||
return next(new LDAP.InsufficientAccessRightsError()) |
|||
} |
|||
|
|||
// Make sure the password matches the resource record
|
|||
if ( !await item.check_password(req.credentials) ) { |
|||
return next(new LDAP.InvalidCredentialsError()) |
|||
} |
|||
|
|||
this.output.success(`Successfully bound resource as DN: ${req.dn.format(this.ldap_dn_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.ldap_dn_format)}`) |
|||
return next(new LDAP.InsufficientAccessRightsError(`Target DN must be a member of the base DN: ${base_dn.format(this.ldap_dn_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 |
|||
|
@ -0,0 +1,13 @@ |
|||
const { Scope } = require('flitter-orm') |
|||
|
|||
/** |
|||
* A flitter-orm scope that enables soft-deletion by an active key. |
|||
* @extends {module:flitter-orm/src/model/Scope~Scope} |
|||
*/ |
|||
class ActiveScope extends Scope { |
|||
async filter(to_filter) { |
|||
return to_filter.equal('active', true) |
|||
} |
|||
} |
|||
|
|||
module.exports = exports = ActiveScope |
File diff suppressed because it is too large
Loading…
Reference in new issue