const { Controller } = require('libflitter') class IAMController extends Controller { static get services() { return [...super.services, 'models', 'canon'] } async check_entity_access(req, res, next) { const Policy = this.models.get('iam:Policy') if ( !req.body.entity_id && !req.body.target_id ) return res.status(400) .message(`${req.T('api.missing_field', true)} entity_id, target_id`) .api() return res.api(await Policy.check_entity_access(req.body.entity_id, req.body.target_id, req.body.permission || undefined)) } async check_user_access(req, res, next) { const User = this.models.get('auth:User') const Policy = this.models.get('iam:Policy') if ( !req.body.target_id ) return res.status(400) .message(`${req.T('api.missing_field')} target_id`) .api() let user = req.user if ( req.body.user_id && req.body.user_id !== 'me' ) user = await User.findById(req.body.user_id) if ( !user ) return res.status(404) .message(req.T('api.user_not_found')) .api() if ( !req.user.can(`auth:user:${user.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await Policy.check_user_access(user, req.body.target_id, req.body.permission || undefined)) } async get_policies(req, res, next) { const Policy = this.models.get('iam:Policy') const policies = await Policy.find({ active: true }) const data = [] for ( const policy of policies ) { if ( req.user.can(`iam:policy:${policy.id}:view`) ) { data.push(await policy.to_api()) } } return res.api(data) } async get_permissions(req, res, next) { const Permission = this.models.get('iam:Permission') const permissions = await Permission.find({ active: true, ...(req.query.target_type ? { target_type: req.query.target_type, } : {}) }) const data = [] for ( const perm of permissions ) { if ( req.user.can(`iam:permission:${perm.target_type}:view`) ) { data.push(await perm.to_api()) } } if ( req.query.include_unset ) { data.reverse().push({ permission: '', }) data.reverse() } return res.api(data) } async get_policy(req, res, next) { const Policy = this.models.get('iam:Policy') const policy = await Policy.findById(req.params.id) if ( !policy ) return res.status(404) .message(req.T('iam.policy_not_found')) .api() if ( !req.user.can(`iam:policy:${policy.id}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await policy.to_api()) } async get_permission(req, res, next) { const Permission = this.models.get('iam:Permission') const permission = await Permission.findById(req.params.id) if ( !permission ) return res.status(404) .message(req.T('iam.permission_not_found')) .api() if ( !req.user.can(`iam:permission:${permission.target_type}:view`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() return res.api(await permission.to_api()) } async create_policy(req, res, next) { const Policy = this.models.get('iam:Policy') const required_fields = ['entity_type', 'entity_id', 'access_type', 'target_type', 'target_id'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } if ( !['user', 'group'].includes(req.body.entity_type) ) return res.status(400) .message(`${req.T('iam.invalid_entity')} user, group`) .api() // Make sure the entity_id is valid if ( req.body.entity_type === 'user' ) { const User = this.models.get('auth:User') const user = await User.findById(req.body.entity_id) if ( !user || !req.user.can(`auth:user:${user.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} entity_id.`) .api() } else if ( req.body.entity_type === 'group' ) { const Group = this.models.get('auth:Group') const group = await Group.findById(req.body.entity_id) if ( !group || !group.active || !req.user.can(`auth:group:${group.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} entity_id.`) .api() } if ( !['allow', 'deny'].includes(req.body.access_type) ) return res.status(400) .message(`${req.T('common.invalid')} access_type. ${req.T('api:must_one')} allow, deny.`) .api() if ( !['application', 'api_scope', 'machine', 'machine_group'].includes(req.body.target_type) ) return res.status(400) .message(`${req.T('common.invalid')} target_type. ${req.T('api:must_one')} application, api_scope, machine, machine_group.`) .api() // Make sure the target_id is valid if ( req.body.target_type === 'application' ) { const Application = this.models.get('Application') const app = await Application.findById(req.body.target_id) if ( !app || !app.active || !req.user.can(`app:${app.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'api_scope' ) { const api_scopes = this.canon.get('controller::api:v1:Reflect.api_scopes')() if ( !api_scopes.includes(req.body.target_id) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'machine' ) { const Machine = this.models.get('ldap:Machine') const machine = await Machine.findById(req.body.target_id) if ( !machine || !machine.active || !req.user.can(`ldap:machine:${machine.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'machine_group' ) { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(req.body.target_id) if ( !group || !group.active || !req.user.can(`ldap:machine_group:${group.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } const policy = new Policy({ entity_type: req.body.entity_type, entity_id: req.body.entity_id, access_type: req.body.access_type, target_type: req.body.target_type, target_id: req.body.target_id, }) if ( req.body.permission ) { // Validate the permission and set it, if it is valid const Permission = this.models.get('iam:Permission') const permission = await Permission.findOne({ active: true, target_type: req.body.target_type, permission: req.body.permission, }) if ( permission ) { policy.for_permission = true policy.permission = req.body.permission } } await policy.save() req.user.allow(`iam:policy:${policy.id}`) await req.user.save() return res.api(await policy.to_api()) } async create_permission(req, res, next) { const Permission = this.models.get('iam:Permission') const required_fields = ['target_type', 'permission'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group'] if ( !valid_target_types.includes(req.body.target_type) ) { return res.status(400) .message(`${req.T('api.invalid_target_type')}`) .api() } if ( !req.user.can(`iam:permission${req.body.target_type}:create`) ) { return res.status(401).api() } // Make sure one doesn't already exist const existing = await Permission.findOne({ active: true, target_type: req.body.target_type, permission: req.body.permission, }) if ( existing ) { return res.status(400) .message(req.T('api.permission_already_exists')) .api() } const perm = new Permission({ target_type: req.body.target_type, permission: req.body.permission, }) await perm.save() return res.api(await perm.to_api()) } async update_policy(req, res, next) { const Policy = this.models.get('iam:Policy') const policy = await Policy.findById(req.params.id) if ( !policy || !policy.active ) return res.status(404) .message(req.T('iam.policy_not_found')) .api() if ( !req.user.can(`iam:policy:${policy.id}:update`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() const required_fields = ['entity_type', 'entity_id', 'access_type', 'target_type', 'target_id'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } if ( !['user', 'group'].includes(req.body.entity_type) ) return res.status(400) .message(`${req.T('common.invalid')} entity_type. ${req.T('api.must_one')} user, group.`) .api() // Make sure the entity_id is valid if ( req.body.entity_type === 'user' ) { const User = this.models.get('auth:User') const user = await User.findById(req.body.entity_id) if ( !user || !req.user.can(`auth:user:${user.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} entity_id.`) .api() } else if ( req.body.entity_type === 'group' ) { const Group = this.models.get('auth:Group') const group = await Group.findById(req.body.entity_id) if ( !group || !group.active || !req.user.can(`auth:group:${group.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} entity_id.`) .api() } if ( !['allow', 'deny'].includes(req.body.access_type) ) return res.status(400) .message(`${req.T('common.invalid')} access_type. ${req.T('api.must_one')} allow, deny.`) .api() if ( !['application', 'api_scope', 'machine', 'machine_group'].includes(req.body.target_type) ) return res.status(400) .message(`${req.T('common.invalid')} target_type. ${req.T('api.must_one')} application, api_scope, machine, machine_group.`) .api() // Make sure the target_id is valid if ( req.body.target_type === 'application' ) { const Application = this.models.get('Application') const app = await Application.findById(req.body.target_id) if ( !app || !app.active || !req.user.can(`app:${app.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'api_scope' ) { const api_scopes = this.canon.get('controller::api:v1:Reflect.api_scopes')() if ( !api_scopes.includes(req.body.target_id) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'machine' ) { const Machine = this.models.get('ldap:Machine') const machine = await Machine.findById(req.body.target_id) if ( !machine || !machine.active || !req.user.can(`ldap:machine:${machine.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } else if ( req.body.target_type === 'machine_group' ) { const MachineGroup = this.models.get('ldap:MachineGroup') const group = await MachineGroup.findById(req.body.target_id) if ( !group || !group.active || !req.user.can(`ldap:machine_group:${group.id}:view`) ) return res.status(400) .message(`${req.T('common.invalid')} target_id.`) .api() } policy.entity_type = req.body.entity_type policy.entity_id = req.body.entity_id policy.access_type = req.body.access_type policy.target_type = req.body.target_type policy.target_id = req.body.target_id if ( req.body.permission ) { // Validate the permission and set it, if it is valid const Permission = this.models.get('iam:Permission') const permission = await Permission.findOne({ active: true, target_type: req.body.target_type, permission: req.body.permission, }) if ( permission ) { policy.for_permission = true policy.permission = req.body.permission } else { policy.for_permission = false policy.permission = undefined } } else { policy.for_permission = false policy.permission = undefined } await policy.save() return res.api() } async update_permission(req, res, next) { const Permission = this.models.get('iam:Permission') const required_fields = ['target_type', 'permission'] for ( const field of required_fields ) { if ( !req.body[field] ) return res.status(400) .message(`${req.T('api.missing_field')} ${field}`) .api() } const valid_target_types = ['application', 'api_scope', 'machine', 'machine_group'] if ( !valid_target_types.includes(req.body.target_type) ) { return res.status(400) .message(`${req.T('api.invalid_target_type')}`) .api() } if ( !req.user.can(`iam:permission${req.body.target_type}:update`) ) { return res.status(401).api() } // Make sure one doesn't already exist const existing = await Permission.findById(req.params.id) if ( !existing?.active ) { return res.status(404) .message(req.T('api.permission_not_found')) .api() } existing.target_type = req.body.target_type existing.permission = req.body.permission await existing.save() return res.api(await existing.to_api()) } async delete_policy(req, res, next) { const Policy = this.models.get('iam:Policy') const policy = await Policy.findById(req.params.id) if ( !policy || !policy.active ) return res.status(404) .message(req.T('iam.policy_not_found')) .api() if ( !req.user.can(`iam:policy:${policy.id}:delete`) ) return res.status(401) .message(req.T('api.insufficient_permissions')) .api() policy.active = false await policy.save() return res.api() } async delete_permission(req, res, next) { const Permission = this.models.get('iam:Permission') const permission = await Permission.findById(req.params.id) if ( !permission?.active ) { return res.status(404) .message(req.T('api.permission_not_found')) .api() } if ( !req.user.can(`iam:permission:${permission.target_type}:delete`) ) { return res.status(401) .message(req.T('api.insufficient_permissions')) .api() } permission.active = false await permission.save() return res.api() } } module.exports = exports = IAMController