--- /dev/null
+/* Copyright (c) 2019 AT&T Intellectual Property. #\r
+# #\r
+# Licensed under the Apache License, Version 2.0 (the "License"); #\r
+# you may not use this file except in compliance with the License. #\r
+# You may obtain a copy of the License at #\r
+# #\r
+# http://www.apache.org/licenses/LICENSE-2.0 #\r
+# #\r
+# Unless required by applicable law or agreed to in writing, software #\r
+# distributed under the License is distributed on an "AS IS" BASIS, #\r
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #\r
+# See the License for the specific language governing permissions and #\r
+# limitations under the License. #\r
+##############################################################################*/\r
+\r
+\r
+const { defineAbilitiesFor } = require('./abilities.js');\r
+const { toMongoQuery } = require('@casl/mongoose')\r
+const getPermissions = require('./get-permissions.js');\r
+const { Forbidden } = require('@feathersjs/errors');\r
+//const pick = require('lodash.pick');\r
+var dot = require('dot-object');\r
+const pick = require('object.pick');\r
+const { ObjectID } = require('mongodb');\r
+\r
+permissions = function (name = null) {\r
+ return async context => {\r
+\r
+ if (!context.params.provider) {\r
+ return Promise.resolve(context);\r
+ }\r
+\r
+ const action = context.method;\r
+ const service = name ? context.app.service(context.path) : context.service;\r
+ const serviceName = name || context.path;\r
+\r
+ let groupQueryParams = JSON.parse(JSON.stringify(context.params));\r
+ groupQueryParams.query = {\r
+ lookup: 'both'\r
+ }\r
+\r
+ //get groups list\r
+ let groups = await context.app.services[context.app.get('base-path') + 'groups'].find(groupQueryParams);\r
+\r
+ //organize permissions for the groups\r
+ let groupsPermissions = getPermissions(context.params.user, groups);\r
+\r
+ //Define Abilities for the user\r
+ const ability = defineAbilitiesFor(context.params.user, groupsPermissions);\r
+\r
+ //Define the fields that they have access to\r
+ let allowedFields;\r
+ if(service.Model){\r
+ allowedFields = service.Model.accessibleFieldsBy(ability, context.method);\r
+ }\r
+\r
+ const throwUnlessCan = (action, resource, field = null) => {\r
+ let instance = resource;\r
+ if(service.Model && typeof resource === 'object'){\r
+ instance = new service.Model(resource);\r
+ }else{\r
+ instance = serviceName;\r
+ }\r
+ \r
+ if (ability.cannot(action, instance, field)) {\r
+ let message = `You are not allowed to ${action} ${serviceName}`;\r
+\r
+ if(field){\r
+ message += ` on field ${field}`;\r
+ }\r
+\r
+ throw new Forbidden(message);\r
+ }\r
+ }\r
+\r
+ context.params.ability = ability;\r
+\r
+\r
+ if (context.method === 'create') {\r
+ throwUnlessCan('create', context.data);\r
+ }\r
+\r
+ if (!context.id) { \r
+\r
+ throwUnlessCan(context.method, serviceName);\r
+ \r
+ const query = toMongoQuery(ability, serviceName, action);\r
+ \r
+ if (query !== null) {\r
+ if(context.params.query.$or && query.$or){\r
+ query.$and = [\r
+ {$or: Object.assign([], context.params.query.$or)},\r
+ {$or: Object.assign([], query.$or)}\r
+ ];\r
+ delete context.params.query.$or;\r
+ delete query.$or;\r
+ }\r
+\r
+ Object.assign(context.params.query, query);\r
+\r
+ } else {\r
+ context.params.query.$limit = 0;\r
+ }\r
+\r
+ if(context.params.query.$select){\r
+ //context.params.query.$select = context.params.query.$select.filter(elem => allowedFields.includes(elem));\r
+ context.params.query.$select = context.params.query.$select.filter(elem => {\r
+ for(let i = 0; i < allowedFields.length; i++){\r
+ \r
+ //if there is dot notation, then it only looks at the parent variable name\r
+ elem = elem.toString().match(new RegExp(/^(\w+)/))[0];\r
+\r
+ if(allowedFields[i] == elem){\r
+ return true;\r
+ }\r
+\r
+ };\r
+\r
+ return false;\r
+ });\r
+ \r
+\r
+\r
+ }else{\r
+ context.params.query.$select = allowedFields;\r
+ }\r
+\r
+ if(context.params.query.$select && context.params.query.$select.length == 0){\r
+ context.params.query.$select = allowedFields;\r
+ }\r
+\r
+ if(!context.params.query.$select){\r
+ context.params.query.$select = [];\r
+ }\r
+ //groupId is used for permissions conditions and must be selected\r
+ if(!context.params.query.$select.includes('groupId')){\r
+ context.params.query.$select.push('groupId');\r
+ }\r
+ \r
+ return context;\r
+ }\r
+\r
+ const params = Object.assign({}, context.params, { provider: null });\r
+\r
+ const result = await service.get(context.id, params);\r
+ throwUnlessCan(action, result);\r
+\r
+ if (action === 'get') {\r
+ context.result = pick(result, allowedFields);\r
+ }else{\r
+ if(context.data){\r
+ Object.keys(context.data).forEach(key => {\r
+ if(key == "$push"){\r
+ Object.keys(context.data['$push']).forEach(k => {\r
+ throwUnlessCan(action, result, k);\r
+ });\r
+ }else{\r
+ throwUnlessCan(action, result, key);\r
+ }\r
+ })\r
+ }\r
+ //context.data = pick(context.data, allowedFields);\r
+ }\r
+\r
+ return context;\r
+\r
+ }\r
+}\r
+\r
+makeObjectIdString = function(obj) {\r
+ for (var property in obj) {\r
+ if (obj.hasOwnProperty(property)) {\r
+ if (typeof obj[property] == "object"){\r
+ if(ObjectID.isValid(obj[property])) {\r
+ obj[property] = obj[property].toString()\r
+ }else{\r
+ makeObjectIdString(obj[property]);\r
+ }\r
+ }\r
+ }\r
+ }\r
+}\r
+\r
+myPick = function(elem, allowedFields){\r
+ //when turning the object into dot notation, we loose the\r
+ makeObjectIdString(elem);\r
+\r
+ let d = dot.dot(elem);\r
+ let toPick = [];\r
+ Object.keys(d).forEach((key) => {\r
+ allowedFields.forEach((f, i) => {\r
+ let r = '^' + f;\r
+ if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){\r
+ toPick.push(key);\r
+ }\r
+ })\r
+ });\r
+ let picked = pick(d, toPick);\r
+ let obj = dot.object(picked)\r
+ return obj;\r
+}\r
+\r
+limitFields = function(){\r
+ return async context => {\r
+ if(context.result.data && context.result.data.length != undefined){\r
+ //checkFields(context.params.ability, context.result.data, context.service.Model);\r
+ context.result.data.forEach((elem, val) => {\r
+ let instance = new context.service.Model(elem);\r
+ const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+ //context.result.data[val] = pick(elem, allowedFields);\r
+ context.result.data[val] = myPick(elem, allowedFields);\r
+ });\r
+ }else if(context.result && context.result.length != undefined){\r
+ context.result.forEach((elem, val) => {\r
+ let instance = new context.service.Model(elem);\r
+ const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+ //context.result[val] = pick(elem, allowedFields);\r
+ context.result[val] = myPick(elem, allowedFields);\r
+ });\r
+ }else if(context.result){\r
+ //checkFields(context.params.ability, context.result, context.service.Model); \r
+ let instance = new context.service.Model(context.result);\r
+ let allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
+ //context.result = pick(context.result, allowedFields);\r
+ context.result = myPick(context.result, allowedFields);\r
+ }\r
+ }\r
+}\r
+\r
+\r
+module.exports = {\r
+ permissions: permissions,\r
+ limitFields: limitFields\r
+}\r