1 /* Copyright (c) 2019 AT&T Intellectual Property. #
\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
7 # http://www.apache.org/licenses/LICENSE-2.0 #
\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
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
26 permissions = function (name = null) {
\r
27 return async context => {
\r
29 if (!context.params.provider) {
\r
30 return Promise.resolve(context);
\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
37 let groupQueryParams = JSON.parse(JSON.stringify(context.params));
\r
38 groupQueryParams.query = {
\r
43 let groups = await context.app.services[context.app.get('base-path') + 'groups'].find(groupQueryParams);
\r
45 //organize permissions for the groups
\r
46 let groupsPermissions = getPermissions(context.params.user, groups);
\r
48 //Define Abilities for the user
\r
49 const ability = defineAbilitiesFor(context.params.user, groupsPermissions);
\r
51 //Define the fields that they have access to
\r
54 allowedFields = service.Model.accessibleFieldsBy(ability, context.method);
\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
62 instance = serviceName;
\r
65 if (ability.cannot(action, instance, field)) {
\r
66 let message = `You are not allowed to ${action} ${serviceName}`;
\r
69 message += ` on field ${field}`;
\r
72 throw new Forbidden(message);
\r
76 context.params.ability = ability;
\r
79 if (context.method === 'create') {
\r
80 throwUnlessCan('create', context.data);
\r
85 throwUnlessCan(context.method, serviceName);
\r
87 const query = toMongoQuery(ability, serviceName, action);
\r
89 if (query !== null) {
\r
90 if(context.params.query.$or && query.$or){
\r
92 {$or: Object.assign([], context.params.query.$or)},
\r
93 {$or: Object.assign([], query.$or)}
\r
95 delete context.params.query.$or;
\r
99 Object.assign(context.params.query, query);
\r
102 context.params.query.$limit = 0;
\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
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
113 if(allowedFields[i] == elem){
\r
125 context.params.query.$select = allowedFields;
\r
128 if(context.params.query.$select && context.params.query.$select.length == 0){
\r
129 context.params.query.$select = allowedFields;
\r
132 if(!context.params.query.$select){
\r
133 context.params.query.$select = [];
\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
143 const params = Object.assign({}, context.params, { provider: null });
\r
145 const result = await service.get(context.id, params);
\r
146 throwUnlessCan(action, result);
\r
148 if (action === 'get') {
\r
149 context.result = pick(result, allowedFields);
\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
158 throwUnlessCan(action, result, key);
\r
162 //context.data = pick(context.data, allowedFields);
\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
177 makeObjectIdString(obj[property]);
\r
184 myPick = function(elem, allowedFields){
\r
185 //when turning the object into dot notation, we loose the
\r
186 makeObjectIdString(elem);
\r
188 let d = dot.dot(elem);
\r
190 Object.keys(d).forEach((key) => {
\r
191 allowedFields.forEach((f, i) => {
\r
193 if(key.replace(/\.([0-9]+)\./g, '.').match(new RegExp(r))){
\r
198 let picked = pick(d, toPick);
\r
199 let obj = dot.object(picked)
\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
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
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
232 permissions: permissions,
\r
233 limitFields: limitFields
\r