added svcapi ui and camunda code
[it/otf.git] / otf-frontend / server / src / feathers / hooks / permissions / permissions.js
1 /*  Copyright (c) 2019 AT&T Intellectual Property.                             #\r
2 #                                                                              #\r
3 #   Licensed under the Apache License, Version 2.0 (the "License");            #\r
4 #   you may not use this file except in compliance with the License.           #\r
5 #   You may obtain a copy of the License at                                    #\r
6 #                                                                              #\r
7 #       http://www.apache.org/licenses/LICENSE-2.0                             #\r
8 #                                                                              #\r
9 #   Unless required by applicable law or agreed to in writing, software        #\r
10 #   distributed under the License is distributed on an "AS IS" BASIS,          #\r
11 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   #\r
12 #   See the License for the specific language governing permissions and        #\r
13 #   limitations under the License.                                             #\r
14 ##############################################################################*/\r
15 \r
16 \r
17 const { defineAbilitiesFor } = require('./abilities.js');\r
18 const { toMongoQuery } = require('@casl/mongoose')\r
19 const getPermissions = require('./get-permissions.js');\r
20 const { Forbidden } = require('@feathersjs/errors');\r
21 //const pick = require('lodash.pick');\r
22 var dot = require('dot-object');\r
23 const pick = require('object.pick');\r
24 const { ObjectID } = require('mongodb');\r
25 \r
26 permissions = function (name = null) {\r
27         return async context => {\r
28 \r
29                 if (!context.params.provider) {\r
30                         return Promise.resolve(context);\r
31                 }\r
32 \r
33                 const action = context.method;\r
34                 const service = name ? context.app.service(context.path) : context.service;\r
35                 const serviceName = name || context.path;\r
36 \r
37                 let groupQueryParams = JSON.parse(JSON.stringify(context.params));\r
38                 groupQueryParams.query = {\r
39                         lookup: 'both'\r
40                 }\r
41 \r
42                 //get groups list\r
43                 let groups = await context.app.services[context.app.get('base-path') + 'groups'].find(groupQueryParams);\r
44 \r
45                 //organize permissions for the groups\r
46                 let groupsPermissions = getPermissions(context.params.user, groups);\r
47 \r
48                 //Define Abilities for the user\r
49                 const ability = defineAbilitiesFor(context.params.user, groupsPermissions);\r
50 \r
51                 //Define the fields that they have access to\r
52                 let allowedFields;\r
53                 if(service.Model){\r
54                         allowedFields = service.Model.accessibleFieldsBy(ability, context.method);\r
55                 }\r
56 \r
57                 const throwUnlessCan = (action, resource, field = null) => {\r
58                         let instance = resource;\r
59                         if(service.Model && typeof resource === 'object'){\r
60                                 instance = new service.Model(resource);\r
61                         }else{\r
62                                 instance = serviceName;\r
63                         }\r
64         \r
65                         if (ability.cannot(action, instance, field)) {\r
66                                 let message = `You are not allowed to ${action} ${serviceName}`;\r
67 \r
68                                 if(field){\r
69                                         message += ` on field ${field}`;\r
70                                 }\r
71 \r
72                                 throw new Forbidden(message);\r
73                         }\r
74                 }\r
75 \r
76                 context.params.ability = ability;\r
77 \r
78 \r
79                 if (context.method === 'create') {\r
80                         throwUnlessCan('create', context.data);\r
81                 }\r
82 \r
83                 if (!context.id) {               \r
84 \r
85                         throwUnlessCan(context.method, serviceName);\r
86                         \r
87                         const query = toMongoQuery(ability, serviceName, action);\r
88                         \r
89                         if (query !== null) {\r
90                                 if(context.params.query.$or && query.$or){\r
91                                         query.$and = [\r
92                                                 {$or: Object.assign([], context.params.query.$or)},\r
93                                                 {$or: Object.assign([], query.$or)}\r
94                                         ];\r
95                                         delete context.params.query.$or;\r
96                                         delete query.$or;\r
97                                 }\r
98 \r
99                                 Object.assign(context.params.query, query);\r
100 \r
101                         } else {\r
102                                 context.params.query.$limit = 0;\r
103                         }\r
104 \r
105                         if(context.params.query.$select){\r
106                                 //context.params.query.$select = context.params.query.$select.filter(elem => allowedFields.includes(elem));\r
107                                 context.params.query.$select = context.params.query.$select.filter(elem => {\r
108                                         for(let i = 0; i < allowedFields.length; i++){\r
109                                                 \r
110                                                 //if there is dot notation, then it only looks at the parent variable name\r
111                                                 elem = elem.toString().match(new RegExp(/^(\w+)/))[0];\r
112 \r
113                                                 if(allowedFields[i] == elem){\r
114                                                         return true;\r
115                                                 }\r
116 \r
117                                         };\r
118 \r
119                                         return false;\r
120                                 });\r
121                                 \r
122 \r
123 \r
124                         }else{\r
125                                 context.params.query.$select = allowedFields;\r
126                         }\r
127 \r
128                         if(context.params.query.$select && context.params.query.$select.length == 0){\r
129                                 context.params.query.$select = allowedFields;\r
130                         }\r
131 \r
132                         if(!context.params.query.$select){\r
133                                 context.params.query.$select = [];\r
134                         }\r
135                         //groupId is used for permissions conditions and must be selected\r
136                         if(!context.params.query.$select.includes('groupId')){\r
137                                 context.params.query.$select.push('groupId');\r
138                         }\r
139                         \r
140                         return context;\r
141                 }\r
142 \r
143                 const params = Object.assign({}, context.params, { provider: null });\r
144 \r
145                 const result = await service.get(context.id, params);\r
146                 throwUnlessCan(action, result);\r
147 \r
148                 if (action === 'get') {\r
149                         context.result = pick(result, allowedFields);\r
150                 }else{\r
151                         if(context.data){\r
152                                 Object.keys(context.data).forEach(key => {\r
153                                         if(key == "$push"){\r
154                                                 Object.keys(context.data['$push']).forEach(k => {\r
155                                                         throwUnlessCan(action, result, k);\r
156                                                 });\r
157                                         }else{\r
158                                                 throwUnlessCan(action, result, key);\r
159                                         }\r
160                                 })\r
161                         }\r
162                         //context.data = pick(context.data, allowedFields);\r
163                 }\r
164 \r
165                 return context;\r
166 \r
167         }\r
168 }\r
169 \r
170 makeObjectIdString = function(obj) {\r
171         for (var property in obj) {\r
172                 if (obj.hasOwnProperty(property)) {\r
173                         if (typeof obj[property] == "object"){\r
174                                 if(ObjectID.isValid(obj[property])) {\r
175                                         obj[property] = obj[property].toString()\r
176                                 }else{\r
177                                         makeObjectIdString(obj[property]);\r
178                                 }\r
179                         }\r
180                 }\r
181         }\r
182 }\r
183 \r
184 myPick = function(elem, allowedFields){\r
185         //when turning the object into dot notation, we loose the\r
186         makeObjectIdString(elem);\r
187 \r
188         let d = dot.dot(elem);\r
189         let toPick = [];\r
190         Object.keys(d).forEach((key) => {\r
191                 allowedFields.forEach((f, i) => {\r
192                         let r = '^' + f;\r
193                         if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){\r
194                                 toPick.push(key);\r
195                         }\r
196                 })\r
197         });\r
198         let picked = pick(d, toPick);\r
199         let obj = dot.object(picked)\r
200         return obj;\r
201 }\r
202 \r
203 limitFields = function(){\r
204         return async context => {\r
205                 if(context.result.data && context.result.data.length != undefined){\r
206                         //checkFields(context.params.ability, context.result.data, context.service.Model);\r
207                         context.result.data.forEach((elem, val) => {\r
208                                 let instance = new context.service.Model(elem);\r
209                                 const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
210                                 //context.result.data[val] = pick(elem, allowedFields);\r
211                                 context.result.data[val] = myPick(elem, allowedFields);\r
212                         });\r
213                 }else if(context.result && context.result.length != undefined){\r
214                         context.result.forEach((elem, val) => {\r
215                                 let instance = new context.service.Model(elem);\r
216                                 const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
217                                 //context.result[val] = pick(elem, allowedFields);\r
218                                 context.result[val] = myPick(elem, allowedFields);\r
219                         });\r
220                 }else if(context.result){\r
221                         //checkFields(context.params.ability, context.result, context.service.Model); \r
222                         let instance = new context.service.Model(context.result);\r
223                         let allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
224                         //context.result = pick(context.result, allowedFields);\r
225                         context.result = myPick(context.result, allowedFields);\r
226                 }\r
227         }\r
228 }\r
229 \r
230 \r
231 module.exports = {\r
232         permissions: permissions,\r
233         limitFields: limitFields\r
234 }\r