openhim-core
Version:
The OpenHIM core application that provides logging and routing of http requests
334 lines (290 loc) • 11.9 kB
JavaScript
import logger from 'winston'
import { ChannelModelAPI } from '../model/channels'
import { ClientModelAPI } from '../model/clients'
import * as authorisation from './authorisation'
import * as utils from '../utils'
/*
* Roles is a virtual API; virtual in the sense that it is not linked
* to a concrete roles collection.
*
* Rather it an abstraction of the 'allow' field on Channels and 'roles' on Clients,
* providing a mechanism for setting up allowed permissions.
*/
function filterRolesFromChannels (channels, clients) {
let cl
let permission
const rolesMap = {} // K: permission, V: channels, clients that share permission
for (const ch of Array.from(channels)) {
for (permission of Array.from(ch.allow)) {
let isClient = false
for (cl of Array.from(clients)) {
if (cl.clientID === permission) {
isClient = true
}
}
if (!isClient) {
if (!rolesMap[permission]) {
rolesMap[permission] = {
channels: [],
clients: []
}
}
rolesMap[permission].channels.push({ _id: ch._id, name: ch.name })
}
}
}
for (cl of Array.from(clients)) {
for (permission of Array.from(cl.roles)) {
if (!rolesMap[permission]) {
rolesMap[permission] = {
channels: [],
clients: []
}
}
rolesMap[permission].clients.push({ _id: cl._id, clientID: cl.clientID })
}
}
const rolesArray = []
for (const role in rolesMap) {
rolesArray.push({
name: role,
channels: rolesMap[role].channels,
clients: rolesMap[role].clients
})
}
return rolesArray
}
export async function getRoles (ctx) {
// Test if the user is authorised
if (!authorisation.inGroup('admin', ctx.authenticated)) {
return utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getRoles denied.`, 'info')
}
try {
const channels = await ChannelModelAPI.find({}, { name: 1, allow: 1 })
const clients = await ClientModelAPI.find({}, { clientID: 1, roles: 1 })
ctx.body = filterRolesFromChannels(channels, clients)
} catch (e) {
logger.error(`Could not fetch roles via the API: ${e.message}`)
ctx.message = e.message
ctx.status = 500
}
}
export async function getRole (ctx, name) {
// Test if the user is authorised
if (!authorisation.inGroup('admin', ctx.authenticated)) {
return utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to getRole denied.`, 'info')
}
try {
const channels = await ChannelModelAPI.find({ allow: { $in: [name] } }, { name: 1 })
const clients = await ClientModelAPI.find({ roles: { $in: [name] } }, { clientID: 1 })
if ((channels === null || channels.length === 0) && (clients === null || clients.length === 0)) {
utils.logAndSetResponse(ctx, 404, `Role with name '${name}' could not be found.`, 'info')
} else {
ctx.body = {
name,
channels: channels.map(r => ({ _id: r._id, name: r.name })),
clients: clients.map(c => ({ _id: c._id, clientID: c.clientID }))
}
}
} catch (e) {
logger.error(`Could not find role with name '${name}' via the API: ${e.message}`)
ctx.body = e.message
ctx.status = 500
}
}
function buildFindChannelByIdOrNameCriteria (ctx, role) {
let criteria = {}
const ids = []
const names = []
for (const ch of Array.from(role.channels)) {
if (ch._id) {
ids.push(ch._id)
} else if (ch.name) {
names.push(ch.name)
} else {
utils.logAndSetResponse(ctx, 400, '_id and/or name must be specified for a channel', 'info')
return null
}
}
if ((ids.length > 0) && (names.length > 0)) {
criteria = {
$or: [
{ _id: { $in: ids } },
{ name: { $in: names } }
]
}
} else {
if (ids.length > 0) {
criteria._id = { $in: ids }
}
if (names.length > 0) {
criteria.name = { $in: names }
}
}
return criteria
}
function buildFindClientByIdOrClientIDCriteria (ctx, role) {
let criteria = {}
const ids = []
const clientIDs = []
for (const ch of Array.from(role.clients)) {
if (ch._id) {
ids.push(ch._id)
} else if (ch.clientID) {
clientIDs.push(ch.clientID)
} else {
utils.logAndSetResponse(ctx, 400, '_id and/or clientID must be specified for a client', 'info')
return null
}
}
if ((ids.length > 0) && (clientIDs.length > 0)) {
criteria = {
$or: [
{ _id: { $in: ids } },
{ clientID: { $in: clientIDs } }
]
}
} else {
if (ids.length > 0) {
criteria._id = { $in: ids }
}
if (clientIDs.length > 0) {
criteria.clientID = { $in: clientIDs }
}
}
return criteria
}
export async function addRole (ctx) {
// Test if the user is authorised
if (!authorisation.inGroup('admin', ctx.authenticated)) {
return utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to addRole denied.`, 'info')
}
const role = ctx.request.body
if (!role.name) {
return utils.logAndSetResponse(ctx, 400, 'Must specify a role name', 'info')
}
const { clients = [], channels = [] } = role || {}
if (clients.length === 0 && channels.length === 0) {
return utils.logAndSetResponse(ctx, 400, 'Must specify at least one channel or client to link the role to', 'info')
}
try {
const chResult = await ChannelModelAPI.find({ allow: { $in: [role.name] } }, { name: 1 })
const clResult = await ClientModelAPI.find({ roles: { $in: [role.name] } }, { clientID: 1 })
if (((chResult != null ? chResult.length : undefined) > 0) || ((clResult != null ? clResult.length : undefined) > 0)) {
return utils.logAndSetResponse(ctx, 400, `Role with name '${role.name}' already exists.`, 'info')
}
const clientConflict = await ClientModelAPI.find({ clientID: role.name }, { clientID: 1 })
if ((clientConflict != null ? clientConflict.length : undefined) > 0) {
return utils.logAndSetResponse(ctx, 409, `A clientID conflicts with role name '${role.name}'. A role name cannot be the same as a clientID.`, 'info')
}
if (channels.length > 0) {
const chCriteria = buildFindChannelByIdOrNameCriteria(ctx, role)
if (!chCriteria) { return }
await ChannelModelAPI.updateMany(chCriteria, { $push: { allow: role.name } })
}
if (clients.length > 0) {
const clCriteria = buildFindClientByIdOrClientIDCriteria(ctx, role)
if (!clCriteria) { return }
await ClientModelAPI.updateMany(clCriteria, { $push: { roles: role.name } })
}
logger.info(`User ${ctx.authenticated.email} setup role '${role.name}'`)
ctx.body = 'Role successfully created'
ctx.status = 201
} catch (e) {
logger.error(`Could not add a role via the API: ${e.message}`)
ctx.body = e.message
ctx.status = 400
}
}
export async function updateRole (ctx, name) {
// Test if the user is authorised
if (!authorisation.inGroup('admin', ctx.authenticated)) {
return utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateRole denied.`, 'info')
}
const role = ctx.request.body
const { channels, clients } = role || {}
try {
// request validity checks
const chResult = await ChannelModelAPI.find({ allow: { $in: [name] } }, { name: 1 })
const clResult = await ClientModelAPI.find({ roles: { $in: [name] } }, { clientID: 1 })
if (chResult.length === 0 && clResult.length === 0) {
return utils.logAndSetResponse(ctx, 404, `Role with name '${name}' could not be found.`, 'info')
}
if (channels != null && channels.length === 0 && clients != null && clients.length === 0) {
return utils.logAndSetResponse(ctx, 400, `Can't have set role '${name}' to have no channels and clients `, 'info')
}
if (clResult.length === 0 && channels != null && channels.length === 0) {
return utils.logAndSetResponse(ctx, 400, `Can't clear channels on '${name}' if it has no clients set`, 'info')
}
if (chResult.length === 0 && clients != null && clients.length === 0) {
return utils.logAndSetResponse(ctx, 400, `Can't clear clients on '${name}' if it has no channels set`, 'info')
}
if (role.name) {
// do check here but only perform rename updates later after channel/client updates
const foundChannels = await ChannelModelAPI.find({ allow: { $in: [role.name] } }, { name: 1 })
const foundClients = await ClientModelAPI.find({ roles: { $in: [role.name] } }, { name: 1 })
if ((foundChannels != null ? foundChannels.length : undefined) > 0 || (foundClients != null ? foundClients.length : undefined > 0)) {
return utils.logAndSetResponse(ctx, 400, `Role with name '${role.name}' already exists.`, 'info')
}
const clientConflict = await ClientModelAPI.find({ clientID: role.name }, { clientID: 1 })
if (clientConflict != null ? clientConflict.length : undefined > 0) {
return utils.logAndSetResponse(ctx, 409, `A clientID conflicts with role name '${role.name}'. A role name cannot be the same as a clientID.`, 'info')
}
}
// TODO : refactor this
if (channels != null) {
const chCriteria = buildFindChannelByIdOrNameCriteria(ctx, role)
if (!chCriteria) { return }
await ChannelModelAPI.updateMany({}, { $pull: { allow: name } })
// set role on channels
if (role.channels.length > 0) {
await ChannelModelAPI.updateMany(chCriteria, { $push: { allow: name } })
}
}
if (clients) {
const clCriteria = buildFindClientByIdOrClientIDCriteria(ctx, role)
if (!clCriteria) { return }
// clear role from existing
await ClientModelAPI.updateMany({}, { $pull: { roles: name } })
// set role on clients
if ((role.clients != null ? role.clients.length : undefined) > 0) {
await ClientModelAPI.updateMany(clCriteria, { $push: { roles: name } })
}
}
// rename role
if (role.name) {
await ChannelModelAPI.updateMany({ allow: { $in: [name] } }, { $push: { allow: role.name } })
await ChannelModelAPI.updateMany({ allow: { $in: [name] } }, { $pull: { allow: name } })
await ClientModelAPI.updateMany({ roles: { $in: [name] } }, { $push: { roles: role.name } })
await ClientModelAPI.updateMany({ roles: { $in: [name] } }, { $pull: { roles: name } })
}
logger.info(`User ${ctx.authenticated.email} updated role with name '${name}'`)
ctx.body = 'Successfully updated role'
ctx.status = 200
} catch (e) {
logger.error(`Could not update role with name '${name}' via the API: ${e.message}`)
ctx.body = e.message
ctx.status = 500
}
}
export async function deleteRole (ctx, name) {
// Test if the user is authorised
if (!authorisation.inGroup('admin', ctx.authenticated)) {
return utils.logAndSetResponse(ctx, 403, `User ${ctx.authenticated.email} is not an admin, API access to updateRole denied.`, 'info')
}
try {
const channels = await ChannelModelAPI.find({ allow: { $in: [name] } }, { name: 1 })
const clients = await ClientModelAPI.find({ roles: { $in: [name] } }, { clientID: 1 })
if ((channels === null || channels.length === 0) && (clients === null || clients.length === 0)) {
return utils.logAndSetResponse(ctx, 404, `Role with name '${name}' could not be found.`, 'info')
}
await ChannelModelAPI.updateMany({}, { $pull: { allow: name } })
await ClientModelAPI.updateMany({}, { $pull: { roles: name } })
logger.info(`User ${ctx.authenticated.email} deleted role with name '${name}'`)
ctx.body = 'Successfully deleted role'
} catch (e) {
logger.error(`Could not update role with name '${name}' via the API: ${e.message}`)
ctx.body = e.message
ctx.status = 500
}
}