added azure related 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                 //see if the entity has already been pulled and stored, else get it\r
144                 if(!context.params.entityData){\r
145                         const params = Object.assign({}, context.params, { provider: null });\r
146                         context.params.entityData = await service.get(context.id, params);\r
147                 }\r
148 \r
149                 throwUnlessCan(action, context.params.entityData);\r
150 \r
151                 if (action === 'get') {\r
152                         context.result = pick(context.params.entityData, allowedFields);\r
153                 }else{\r
154                         if(context.data){\r
155                                 Object.keys(context.data).forEach(key => {\r
156                                         if(key == "$push"){\r
157                                                 Object.keys(context.data['$push']).forEach(k => {\r
158                                                         throwUnlessCan(action, context.params.entityData, k);\r
159                                                 });\r
160                                         }else{\r
161                                                 throwUnlessCan(action, context.params.entityData, key);\r
162                                         }\r
163                                 })\r
164                         }\r
165                         //context.data = pick(context.data, allowedFields);\r
166                 }\r
167 \r
168                 return context;\r
169 \r
170         }\r
171 }\r
172 \r
173 makeObjectIdString = function(obj) {\r
174         for (var property in obj) {\r
175                 if (obj.hasOwnProperty(property)) {\r
176                         if (typeof obj[property] == "object"){\r
177                                 if(ObjectID.isValid(obj[property])) {\r
178                                         obj[property] = obj[property].toString()\r
179                                 }else{\r
180                                         makeObjectIdString(obj[property]);\r
181                                 }\r
182                         }\r
183                 }\r
184         }\r
185 }\r
186 \r
187 myPick = function(elem, allowedFields){\r
188         //when turning the object into dot notation, we loose the\r
189         makeObjectIdString(elem);\r
190 \r
191         let d = dot.dot(elem);\r
192         let toPick = [];\r
193         Object.keys(d).forEach((key) => {\r
194                 allowedFields.forEach((f, i) => {\r
195                         let r = '^' + f;\r
196                         if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){\r
197                                 toPick.push(key);\r
198                         }\r
199                 })\r
200         });\r
201         let picked = pick(d, toPick);\r
202         let obj = dot.object(picked)\r
203         return obj;\r
204 }\r
205 \r
206 limitFields = function(){\r
207         return async context => {\r
208                 if(context.result.data && context.result.data.length != undefined){\r
209                         //checkFields(context.params.ability, context.result.data, context.service.Model);\r
210                         context.result.data.forEach((elem, val) => {\r
211                                 let instance = new context.service.Model(elem);\r
212                                 const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
213                                 //context.result.data[val] = pick(elem, allowedFields);\r
214                                 context.result.data[val] = myPick(elem, allowedFields);\r
215                         });\r
216                 }else if(context.result && context.result.length != undefined){\r
217                         context.result.forEach((elem, val) => {\r
218                                 let instance = new context.service.Model(elem);\r
219                                 const allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
220                                 //context.result[val] = pick(elem, allowedFields);\r
221                                 context.result[val] = myPick(elem, allowedFields);\r
222                         });\r
223                 }else if(context.result){\r
224                         //checkFields(context.params.ability, context.result, context.service.Model); \r
225                         let instance = new context.service.Model(context.result);\r
226                         let allowedFields = instance.accessibleFieldsBy(context.params.ability);\r
227                         //context.result = pick(context.result, allowedFields);\r
228                         context.result = myPick(context.result, allowedFields);\r
229                 }\r
230         }\r
231 }\r
232 \r
233 \r
234 module.exports = {\r
235         permissions: permissions,\r
236         limitFields: limitFields\r
237 }\r