const { Controller } = require('libflitter') const zxcvbn = require('zxcvbn') class LDAPController extends Controller { static get services() { return [...super.services, 'models', 'utility', 'configs'] } async get_config(req, res, next) { // ldap port // user base dn // group base dn const config = this.configs.get('ldap:server') return res.api({ port: config.port, base_dc: config.schema.base_dc, authentication_base: config.schema.authentication_base, group_base: config.schema.group_base, login_field: config.schema.auth.user_id, }) } async get_clients(req, res, next) { const Client = this.models.get('ldap:Client') const clients = await Client.find({active: true}) const data = [] for ( const client of clients ) { if ( !req.user.can(`ldap:client:${client.id}:view`) ) continue data.push(await client.to_api()) } return res.api(data) } async get_groups(req, res, next) { const Group = this.models.get('ldap:Group') const groups = await Group.find({active: true}) const data = [] for ( const group of groups ) { if ( !req.user.can(`ldap:group:${group.id}:view`) ) continue data.push(await group.to_api()) } return res.api(data) } async get_machines(req, res, next) { const Machine = this.models.get('ldap:Machine') const machines = await Machine.find({active: true}) const data = [] for ( const machine of machines ) { if ( !req.user.can(`ldap:machine:${machine.id}:view`) ) continue data.push(await machine.to_api()) } return res.api(data) } async get_machine_groups(req, res, next) { const MachineGroup = this.models.get('ldap:MachineGroup') const groups = await MachineGroup.find({active: true}) const data = [] for ( const group of groups ) { if ( !req.user.can(`ldap:machine_group:${group.id}:view`) ) continue data.push(await group.to_api()) } return res.api(data) } async get_client(req, res, next) { const Client = this.models.get('ldap:Client') const client = await Client.findById(req.params.id) if ( !client || !client.active ) return res.status(404) .message(req.T('api.client_not_found')) .api() if ( !req.user.can(`ldap:client:${client.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await client.to_api()) } async get_group(req, res, next) { const Group = this.models.get('ldap:Group') const group = await Group.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:group:${group.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await group.to_api()) } async get_machine(req, res, next) { const Machine = this.models.get('ldap:Machine') const machine = await Machine.findById(req.params.id) if ( !machine || !machine.active ) return res.status(404) .message(req.T('api.machine_not_found')) .api() if ( !req.user.can(`ldap:machine:${machine.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await machine.to_api()) } async get_machine_group(req, res, next) { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:machine_group:${group.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await group.to_api()) } async create_client(req, res, next) { if ( !req.user.can('ldap:client:create') ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() // validate inputs const required_fields = ['name', 'uid', 'password'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) } // Make sure the uid is free const User = this.models.get('auth:User') const existing_user = await User.findOne({ uid: req.body.uid.toLowerCase() }) if ( existing_user ) return res.status(400) .message(req.T('api.user_already_exists')) .api() // Verify password complexity const min_score = 3 const result = zxcvbn(req.body.password) if ( result.score < min_score ) return res.status(400) .message(req.T('auth.password_complexity_fail').replace('MIN_SCORE', min_score)) .api() // Create the client const Client = this.models.get('ldap:Client') const client = await Client.create({ uid: req.body.uid.toLowerCase(), password: req.body.password, name: req.body.name, }) return res.api(await client.to_api()) } async create_machine(req, res, next) { // validate inputs const required_fields = ['name', 'description'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the machine name is free const Machine = this.models.get('ldap:Machine') const existing_machine = await Machine.findOne({ name: req.body.name }) if ( existing_machine ) return res.status(400) .message(req.T('api.machine_already_exists')) .api() const machine = new Machine({ name: req.body.name, description: req.body.description, host_name: req.body.host_name, location: req.body.location, }) if ( req.body.bind_password ) { await machine.set_bind_password(req.body.bind_password) } if ( 'ldap_visible' in req.body ) { machine.ldap_visible = !!req.body.ldap_visible } await machine.save() return res.api(await machine.to_api()) } async create_machine_group(req, res, next) { // validate inputs const required_fields = ['name'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the machine name is free const MachineGroup = this.models.get('ldap:MachineGroup') const existing_group = await MachineGroup.findOne({ name: req.body.name }) if ( existing_group ) return res.status(400) .message(req.T('api.group_already_exists')) .api() const group = new MachineGroup({ name: req.body.name, description: req.body.description, }) if ( 'ldap_visible' in req.body ) { group.ldap_visible = !!req.body.ldap_visible } const Machine = this.models.get('ldap:Machine') const machine_ids = Array.isArray(req.body.machine_ids) ? req.body.machine_ids : [] group.machine_ids = [] for ( const potential of machine_ids ) { const machine = await Machine.findOne({ _id: Machine.to_object_id(potential), active: true, }) if ( machine ) { group.machine_ids.push(potential) } } await group.save() return res.api(await group.to_api()) } async create_group(req, res, next) { // validate inputs const required_fields = ['role', 'name'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the group name is free const Group = this.models.get('ldap:Group') const User = this.models.get('auth:User') const existing_group = await Group.findOne({ name: req.body.name }) if ( existing_group ) return res.status(400) .message(req.T('api.group_already_exists')) .api() // Make sure the role exists if ( !this.configs.get('auth.roles')[req.body.role] ) return res.status(400) .message(`${req.T('common.invalid')} role.`) .api() const group = new Group({ name: req.body.name, role: req.body.role, }) if ( 'ldap_visible' in req.body ) group.ldap_visible = !!req.body.ldap_visible if ( 'user_ids' in req.body ) { // Attempt to parse the user_ids const parsed = typeof req.body.user_ids === 'string' ? this.utility.infer(req.body.user_ids) : req.body.user_ids const user_ids = Array.isArray(parsed) ? parsed : [parsed] // Make sure all the user IDs are valid for ( const user_id of user_ids ) { const user = await User.findById(user_id) if ( !user ) return res.status(400) .message(`${req.T('common.invalid')} user_id: ${user_id}`) .api() } group.user_ids = user_ids } await group.save() return res.api(await group.to_api()) } async update_client(req, res, next) { const Client = this.models.get('ldap:Client') const client = await Client.findById(req.params.id) if ( !client || !client.active ) return res.status(404) .message(req.T('api.client_not_found')) .api() if ( !req.user.can(`ldap:client:${client.id}:update`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const required_fields = ['name', 'uid'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } const user = await client.user() // Update the name if ( req.body.name !== client.name ) { client.name = req.body.name user.first_name = req.body.name } // Update the uid if ( req.body.uid.toLowerCase() !== user.uid ) { // Make sure the UID is free const User = this.models.get('auth:User') const existing_user = await User.findOne({ uid: req.body.uid.toLowerCase() }) if ( existing_user ) return res.status(400) .message(req.T('api.user_already_exists')) .api() user.uid = req.body.uid.toLowerCase() } // Update the password if ( req.body.password && !(await user.check_password(req.body.password)) ) { // Verify the password's complexity const min_score = 3 const result = zxcvbn(req.body.password) if ( result.score < min_score ) return res.status(400) .message(req.T('auth.password_complexity_fail').replace('MIN_SCORE', min_score)) .api() await user.reset_password(req.body.password) } await user.save() await client.save() return res.api() } async update_machine(req, res, next) { const Machine = this.models.get('ldap:Machine') const machine = await Machine.findById(req.params.id) if ( !machine || !machine.active ) return res.status(404) .message(req.T('api.machine_not_found')) .api() if ( !req.user.can(`ldap:machine:${machine.id}:update`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const required_fields = ['name', 'description'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the machine name is free const existing_machine = await Machine.findOne({ name: req.body.name }) if ( existing_machine && existing_machine.id !== machine.id ) return res.status(400) .message(req.T('api.machine_already_exists')) .api() machine.name = req.body.name machine.description = req.body.description machine.host_name = req.body.host_name machine.location = req.body.location if ( req.body.bind_password ) { await machine.set_bind_password(req.body.bind_password) } if ( 'ldap_visible' in req.body ) { machine.ldap_visible = !!req.body.ldap_visible } await machine.save() return res.api(await machine.to_api()) } async update_machine_group(req, res, next) { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:machine_group:${group.id}:update`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const required_fields = ['name'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the machine name is free const existing_group = await MachineGroup.findOne({ name: req.body.name }) if ( existing_group && existing_group.id !== group.id ) return res.status(400) .message(req.T('api.group_already_exists')) .api() group.name = req.body.name group.description = req.body.description if ( 'ldap_visible' in req.body ) { group.ldap_visible = !!req.body.ldap_visible } const Machine = this.models.get('ldap:Machine') const machine_ids = Array.isArray(req.body.machine_ids) ? req.body.machine_ids : [] group.machine_ids = [] for ( const potential of machine_ids ) { const machine = await Machine.findOne({ _id: Machine.to_object_id(potential), active: true, }) if ( machine ) { group.machine_ids.push(potential) } } await group.save() return res.api(await group.to_api()) } async update_group(req, res, next) { const User = await this.models.get('auth:User') const Group = await this.models.get('ldap:Group') const group = await Group.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:group:${group.id}:update`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const required_fields = ['role', 'name'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } // Make sure the name is free const existing_group = await Group.findOne({ name: req.body.name }) if ( existing_group && existing_group.id !== group.id ) return res.status(400) .message(req.T('api.group_already_exists')) .api() group.name = req.body.name group.role = req.body.name group.ldap_visible = !!req.body.ldap_visible if ( req.body.user_ids ) { const parsed = typeof req.body.user_ids === 'string' ? this.utility.infer(req.body.user_ids) : req.body.user_ids const user_ids = Array.isArray(parsed) ? parsed : [parsed] for ( const user_id of user_ids ) { const user = await User.findById(user_id) if ( !user ) return res.status(400) .message(`${req.T('common.invalid')} user_id: ${user_id}`) .api() } group.user_ids = user_ids } else { group.user_ids = [] } await group.save() return res.api() } async delete_client(req, res, next) { const Client = this.models.get('ldap:Client') const client = await Client.findById(req.params.id) if ( !client || !client.active ) return res.status(404) .message(req.T('api.client_not_found')) .api() if ( !req.user.can(`ldap:client:${client.id}:delete`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const user = await client.user() client.active = false user.active = false user.block_login = true await user.save() await client.save() return res.api() } async delete_group(req, res, next) { const Group = this.models.get('ldap:Group') const group = await Group.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:group:${group.id}:delete`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() group.active = false await group.save() return res.api() } async delete_machine(req, res, next) { const Machine = this.models.get('ldap:Machine') const machine = await Machine.findById(req.params.id) if ( !machine || !machine.active ) return res.status(404) .message(req.T('api.machine_not_found')) .api() if ( !req.user.can(`ldap:machine:${machine.id}:delete`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() machine.active = false await machine.save() return res.api() } async delete_machine_group(req, res, next) { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(req.params.id) if ( !group || !group.active ) return res.status(404) .message(req.T('api.group_not_found')) .api() if ( !req.user.can(`ldap:machine_group:${group.id}:delete`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() group.active = false await group.save() return res.api() } } module.exports = exports = LDAPController