UNPKG

backendless-console-sdk

Version:

Backendless Console SDK for Node.js and browser

364 lines (295 loc) 11.9 kB
import _sortBy from 'lodash/sortBy' import totalRows from './utils/total-rows' import { buildGetUrl, buildPutUrl, buildDeleteUrl } from './security-url-builder' import { SYSTEM_ROLES, ALL_OBJECTS, PermissionPolicies } from './constants/security' import urls from './urls' const emptyResponse = { data: [] } const sortEntitiesByName = response => { response.data = _sortBy(response.data, 'name') return response } const castArray = items => Array.isArray(items) ? items : [items] const transformOwnerResponse = response => ({ data: [{ permissions: response }] }) const transformRolesResponse = response => ({ data: response }) const transformUsersResponse = response => ({ data: response }) const transformColumnsResponse = response => { return { data: response.map(item => ({ name : item.roleName, roleId : item.roleId, permissions: item.permissions })) } } /** * Transforms * [{operationName, operationId, roles: [{roleId, roleName, access}, ...]}, ...] * into * { data: [ { roleId, name, permissions: [{operation, access}, ..] }] } * * @param {Array} response * @returns {{data: Array}} */ const transformAclRolesResponse = response => { const rolesMap = {} response.forEach(operation => { operation.roles.forEach(role => { if (!rolesMap[role.roleId]) { rolesMap[role.roleId] = { roleId: role.roleId, name: role.roleName, permissions: [] } } rolesMap[role.roleId].permissions.push({ operation: operation.operationName, access : role.access }) }) }) return transformRolesResponse(Object.keys(rolesMap).map(roleId => rolesMap[roleId])) } /** * Here is the situation : * * for users the response is { data: [ { permissions: [{operation, access}, ..] }] } * for roles the response is [ {permissions: [{operation, access}, ...]} ] * for owner the response is [{ operation, access }, ...] * * for acl roles the response is absolute EVIL in absolutely CRAZY form : * [{operationName, operationId, [roles: [{roleId, roleName, access}]]}] * * * Since, we want to store, handle and render all permissions using the same set of classes/components * we need to have the response to be the same for all policies : * * { data: [ { permissions: [{operation, access}, ..] }] } * * This method does exactly this * * @param {*} response * @returns {{data: Array.<{permissions: Array}>}} */ const alignGetResponseShape = response => { if (response.data) { return response //a normal response, nothing to do here } const empty = !response.length const columnsResponse = !empty && !!response[0].permissions && !!response[0].permissions[0].columnId const rolesResponse = !empty && !!response[0].permissions const aclRolesResponse = !empty && !!response[0].operationId const usersResponse = !empty && !!response[0].userId return (empty && emptyResponse) || (columnsResponse && transformColumnsResponse(response)) || (rolesResponse && transformRolesResponse(response)) || (aclRolesResponse && transformAclRolesResponse(response)) || (usersResponse && transformUsersResponse(response)) || transformOwnerResponse(response) } const enrichPermissions = result => { const columnsResponse = !!result.data[0]?.permissions[0]?.columnId result.data.forEach(item => { item.permissions = toPermissionsMap(item.permissions, columnsResponse) }) return result } const enrichEntities = result => { result.data.forEach(item => { item.id = item.userId || item.roleId || 'owner' }) return result } /** * For owner the response is 'GRANT_INHERIT' * for roles acl the response is complete GET response * for all other types the response is array of objects with two fields {operation, access} * possibly wrapped into 'permissions' field.. ︻デ═一 * * We want to cast all these responses to the following structure * * { * [policyItemId]: { * [operation] : {access}, * ... * }, * ... * } */ const alignModifyResponseShape = (appId, policy, policyItemId, service, serviceItemId, serviceItemName, objectId, operation) => response => { if (!response) { return {} } const isOwnerPolicy = policy === PermissionPolicies.OWNER const isRolesPolicy = policy === PermissionPolicies.ROLES const isColumnsPolicy = policy === PermissionPolicies.COLUMNS const isObjectACL = objectId !== ALL_OBJECTS const result = {} if (isOwnerPolicy) { result.owner = { [operation]: response } } else if (isObjectACL && isRolesPolicy) { response.forEach(operation => { operation.roles.forEach(role => { result[role.roleId] = result[role.roleId] || {} result[role.roleId][operation.operationName] = role.access }) }) } else if (isColumnsPolicy) { return response } else { if (response.permissions) { response = response.permissions } const permissions = result[policyItemId] = {} response.forEach(permission => permissions[permission.operation] = permission.access) } return result } /** * Converts permissions object to permissions map where key is operation and value is access * E.q. * [{operation: 'Update', access: 'Grant}, {operation: 'Find', access: 'Deny'}] * => * {Update: 'Grant', Find: 'Deny'} * @param permissions * @param columnsResponse * @returns {{}} */ const toPermissionsMap = (permissions, columnsResponse) => { const map = {} if (permissions) { permissions.forEach(permission => { if (columnsResponse) { map[permission.columnId] = permission.access } else { map[permission.operation] = permission.access } }) } return map } //TODO it will be removed when the server will be ready (CONSOLE-307) const normalizeRolePropsNames = role => ({ id: role.roleId, name: role.rolename, ...role }) //TODO it will be removed when the server will be ready (CONSOLE-307) const normalizeRolesPropsNames = roles => roles.map(normalizeRolePropsNames) //TODO it will be removed when the server will be ready to provide this info (CONSOLE-307) const enrichRolesProps = roles => roles.map(role => ({ system: SYSTEM_ROLES.includes(role.name), ...role })) export default req => { const loadPermissions = (appId, policy, service, serviceItemId, serviceItemName, objectId, filterParams = {}, identityColumnName) => { const url = buildGetUrl(appId, policy, service, serviceItemId, serviceItemName, objectId, filterParams) const addTotalRows = response => { if (policy === PermissionPolicies.USERS) { filterParams.identity = filterParams.identity || filterParams.name const usersCountReq = req .get(urls.dataTable(appId, 'Users')) .query({ where: filterParams.identity ? `${identityColumnName} like '%${filterParams.identity}%'` : undefined }) return totalRows(req).getFor(usersCountReq) .then(totalRows => ({ ...response, totalRows })) } return response } return req.get(url) .then(alignGetResponseShape) //transform all policies responses to the same shape with 'data' prop .then(enrichPermissions) //transform permissions array into permissions map .then(enrichEntities) //transform users and roles entities to the shape with 'id' property .then(addTotalRows) //resolve totalRows property .then(sortEntitiesByName) } const setPermission = (appId, policy, policyItemId, service, serviceItemId, serviceItemName, objectId, permission) => { const isOwnerPolicy = policy === PermissionPolicies.OWNER const isColumnPolicy = policy === PermissionPolicies.COLUMNS const isObjectACL = objectId !== ALL_OBJECTS //for owner and object acl the body should contain just {access, permission} object //for all other cases it must be wrapped into an array and object : //{ permissions: [{access,permission}, ...] } const body = (isOwnerPolicy || isColumnPolicy || isObjectACL) ? permission : { permissions: castArray(permission) } const url = buildPutUrl(appId, policy, service, serviceItemId, serviceItemName, objectId, policyItemId, permission) return req.put(url, body) .then(alignModifyResponseShape( appId, policy, policyItemId, service, serviceItemId, serviceItemName, objectId, permission )) } const dropPermissions = (...args) => { const url = buildDeleteUrl(...args) return req.delete(url).then(alignModifyResponseShape(...args)) } const searchDataACLUsers = (appId, tableName, objectId, query) => { return req.get(`${ urls.security(appId) }/data/${ tableName }/objectAcl/${ objectId }/users/search/${ query }`) .then(result => ({ data: result })) .then(enrichPermissions) .then(enrichEntities) .then(sortEntitiesByName) } const loadRoles = appId => req.get(urls.securityRoles(appId)) .then(normalizeRolesPropsNames) //TODO it will be removed when the server will be ready (CONSOLE-307) .then(enrichRolesProps) //TODO it will be removed when the server will be ready (CONSOLE-307) const createRole = (appId, name) => req.put(`${ urls.securityRoles(appId) }/${ encodeURIComponent(name) }`) .then(normalizeRolePropsNames) const deleteRole = (appId, id) => req.delete(`${ urls.securityRoles(appId) }/${ id }`) const loadRolePermissions = (appId, id) => req.get(`${ urls.securityRoles(appId) }/permissions/${ id }`) const setRolePermission = (appId, id, permission) => req.put(`${ urls.securityRoles(appId) }/permissions/${ id }`, permission) const loadColumnPermissions = (appId, tableId) => req.get(`${ urls.security(appId) }/data/${ tableId }/columns/permissions`) const loadAuditLogs = appId => req.get(`${urls.security(appId)}/audit-logs`) const deleteAuditLogs = appId => req.delete(`${urls.security(appId)}/audit-logs`) const downloadAuditLogs = (appId, fromDate, toDate) => { return req.get(`${urls.security(appId)}/audit-logs/download`).query({ fromDate, toDate }) } const activatePanicMode = (appId, settings) => req.put(`${ urls.appConsole(appId) }/panic/enable`, settings) const deactivatePanicMode = (appId, settings) => req.put(`${ urls.appConsole(appId) }/panic/disable`, settings) const loadUsers = (appId, { identityOrUserId, offset, pageSize, sortBy }) => { return req.get(`${urls.appConsole(appId)}/user/sessions/users`) .query({ identityOrUserId, offset, pageSize, sortBy }) } const loadUsersWithSessions = (appId, { cursor, pageSize }) => { return req.get(`${urls.appConsole(appId)}/user/sessions/users-with-sessions`) .query({ pageSize, cursor }) } const loadUserSessions = (appId, userId, { cursor, pageSize }) => { return req.get(`${ urls.appConsole(appId) }/user/sessions/${ userId }`) .query({ pageSize, cursor }) } const logoutUserSessions = (appId, userId) => { return req.put(`${ urls.appConsole(appId) }/user/sessions/logout`, userId) } const activateHIPAACompliance = appId => { return req.put(`${ urls.appConsole(appId) }/compliance/hipaa/enable`) } const deactivateHIPAACompliance = appId => { return req.put(`${ urls.appConsole(appId) }/compliance/hipaa/disable`) } return { loadRoles, createRole, deleteRole, loadRolePermissions, loadPermissions, setRolePermission, setPermission, dropPermissions, searchDataACLUsers, loadColumnPermissions, loadAuditLogs, deleteAuditLogs, downloadAuditLogs, activatePanicMode, deactivatePanicMode, loadUsers, loadUsersWithSessions, loadUserSessions, logoutUserSessions, activateHIPAACompliance, deactivateHIPAACompliance, } }