Skip to content

Commit

Permalink
write permissions for sample PUT, PATCH and DELETE
Browse files Browse the repository at this point in the history
  • Loading branch information
shriramshankar committed Apr 5, 2017
1 parent c61a0f0 commit 9da8928
Show file tree
Hide file tree
Showing 9 changed files with 367 additions and 16 deletions.
4 changes: 3 additions & 1 deletion api/v1/controllers/samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,9 @@ module.exports = {
let delRlinksPromise;
if (featureToggles.isFeatureEnabled(constants.featureName) &&
helper.modelName === 'Sample') {
delRlinksPromise = redisModelSample.deleteSampleRelatedLinks(params);
delRlinksPromise = u.getUserNameFromToken(req,
featureToggles.isFeatureEnabled('enforceWritePermission'))
.then((user) => redisModelSample.deleteSampleRelatedLinks(params, user));
} else {
delRlinksPromise = u.findByKey(helper, params)
.then((o) => u.isWritable(req, o,
Expand Down
4 changes: 3 additions & 1 deletion api/v1/helpers/verbs/doDelete.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ function doDelete(req, res, next, props) {
if (featureToggles.isFeatureEnabled(constants.featureName) &&
props.modelName === 'Sample') {
const sampleName = req.swagger.params.key.value.toLowerCase();
delPromise = redisModelSample.deleteSample(sampleName);
delPromise = u.getUserNameFromToken(req,
featureToggles.isFeatureEnabled('enforceWritePermission'))
.then((user) => redisModelSample.deleteSample(sampleName, user));
} else {
delPromise = u.findByKey(
props, req.swagger.params
Expand Down
4 changes: 3 additions & 1 deletion api/v1/helpers/verbs/doPatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ function doPatch(req, res, next, props) {
u.checkDuplicateRLinks(rLinks);
}

patchPromise = redisModelSample.patchSample(req.swagger.params);
patchPromise = u.getUserNameFromToken(req,
featureToggles.isFeatureEnabled('enforceWritePermission'))
.then((user) => redisModelSample.patchSample(req.swagger.params, user));
} else {
patchPromise = u.findByKey(
props, req.swagger.params
Expand Down
4 changes: 3 additions & 1 deletion api/v1/helpers/verbs/doPut.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ function doPut(req, res, next, props) {
u.checkDuplicateRLinks(rLinks);
}

putPromise = redisModelSample.putSample(req.swagger.params);
putPromise = u.getUserNameFromToken(req,
featureToggles.isFeatureEnabled('enforceWritePermission'))
.then((user) => redisModelSample.putSample(req.swagger.params, user));
} else {
const puttableFields =
req.swagger.params.queryBody.schema.schema.properties;
Expand Down
84 changes: 73 additions & 11 deletions cache/models/samples.js
Original file line number Diff line number Diff line change
Expand Up @@ -478,9 +478,10 @@ module.exports = {
* sample index, delete aspect from subject set and delete sample hash. If
* sample not found, throw Not found error.
* @param {String} sampleName - Sample name
* @param {String} userName - The user performing the write operation
* @returns {Promise} - Resolves to a sample object
*/
deleteSample(sampleName) {
deleteSample(sampleName, userName) {
const subjAspArr = sampleName.toLowerCase().split('|');

if (subjAspArr.length < TWO) {
Expand All @@ -494,8 +495,22 @@ module.exports = {
const aspName = subjAspArr[ONE];
const cmds = [];
let sampObjToReturn;
const hasWritePerm = featureToggles
.isFeatureEnabled('enforceWritePermission') ?
sampleStore.isSampleWritable(aspectHelper.model,
aspName, userName) : Promise.resolve(true);
return hasWritePerm
.then((ok) => {
if (!ok) {
const err = new redisErrors.UpdateDeleteForbidden({
explanation: `The user: ${userName}, does not have write permission` +
` on the sample: ${sampleName}`,
});
return Promise.reject(err);
}

return redisOps.getHashPromise(sampleType, sampleName)
return redisOps.getHashPromise(sampleType, sampleName);
})
.then((sampleObj) => {
if (!sampleObj) {
throw new redisErrors.ResourceNotFoundError({
Expand Down Expand Up @@ -523,14 +538,30 @@ module.exports = {
/**
* Delete sample related links
* @param {Object} params - Request parameters
* @param {String} userName - The user performing the write operation
* @returns {Promise} - Resolves to a sample object
*/
deleteSampleRelatedLinks(params) {
deleteSampleRelatedLinks(params, userName) {
const sampleName = params.key.value;
const aspectName = sampleName.split('|')[ONE];
let currSampObj;
let aspectObj;
const hasWritePerm = featureToggles
.isFeatureEnabled('enforceWritePermission') ?
sampleStore.isSampleWritable(aspectHelper.model,
aspectName, userName) : Promise.resolve(true);
return hasWritePerm
.then((ok) => {
if (!ok) {
const err = new redisErrors.UpdateDeleteForbidden({
explanation: `The user: ${userName}, does not have write permission` +
` on the sample: ${sampleName}`,
});
return Promise.reject(err);
}

return redisOps.getHashPromise(sampleType, sampleName)
return redisOps.getHashPromise(sampleType, sampleName);
})
.then((sampObj) => {
if (!sampObj) {
throw new redisErrors.ResourceNotFoundError({
Expand All @@ -539,7 +570,6 @@ module.exports = {
}

currSampObj = sampObj;
const aspectName = sampleName.split('|')[ONE];
return redisOps.getHashPromise(aspectType, aspectName);
})
.then((aspObj) => {
Expand Down Expand Up @@ -586,15 +616,31 @@ module.exports = {
* response. Note: Message body and message code will be updated if provided,
* else no fields for message body/message code in redis object.
* @param {Object} params - Request parameters
* @param {String} userName - The user performing the write operation
* @returns {Promise} - Resolves to a sample object
*/
patchSample(params) {
patchSample(params, userName) {
const sampleName = params.key.value;
const aspectName = sampleName.split('|')[ONE];
const reqBody = params.queryBody.value;
let currSampObj;
let aspectObj;
const hasWritePerm = featureToggles
.isFeatureEnabled('enforceWritePermission') ?
sampleStore.isSampleWritable(aspectHelper.model,
aspectName, userName) : Promise.resolve(true);
return hasWritePerm
.then((ok) => {
if (!ok) {
const err = new redisErrors.UpdateDeleteForbidden({
explanation: `The user: ${userName}, does not have write permission` +
` on the sample: ${sampleName}`,
});
return Promise.reject(err);
}

return redisOps.getHashPromise(sampleType, sampleName)
return redisOps.getHashPromise(sampleType, sampleName);
})
.then((sampObj) => {
if (!sampObj) {
throw new redisErrors.ResourceNotFoundError({
Expand All @@ -603,7 +649,7 @@ module.exports = {
}

currSampObj = sampObj;
const aspectName = sampleName.split('|')[ONE];

return redisOps.getHashPromise(aspectType, aspectName);
})
.then((aspObj) => {
Expand Down Expand Up @@ -650,6 +696,7 @@ module.exports = {
* to get sample from Redis. Is sample found, throw error, else create
* sample. Update sample index and subject set as well.
* @param {Object} params - Request parameters
* @param {String} userName - The user performing the write operation
* @returns {Promise} - Resolves to a sample object
*/
postSample(params) {
Expand Down Expand Up @@ -737,15 +784,31 @@ module.exports = {
* if needed. Delete message body and message code fields from hash
* if not provided because they will be null. Then update sample.
* @param {Object} params - Request parameters
* @param {String} userName - The user performing the write operation
* @returns {Promise} - Resolves to a sample object
*/
putSample(params) {
putSample(params, userName) {
const sampleName = params.key.value;
const aspectName = sampleName.split('|')[ONE];
const reqBody = params.queryBody.value;
let currSampObj;
let aspectObj;
const hasWritePerm = featureToggles
.isFeatureEnabled('enforceWritePermission') ?
sampleStore.isSampleWritable(aspectHelper.model,
aspectName, userName) : Promise.resolve(true);
return hasWritePerm
.then((ok) => {
if (!ok) {
const err = new redisErrors.UpdateDeleteForbidden({
explanation: `The user: ${userName}, does not have write permission` +
` on the sample: ${sampleName}`,
});
return Promise.reject(err);
}

return redisOps.getHashPromise(sampleType, sampleName)
return redisOps.getHashPromise(sampleType, sampleName);
})
.then((sampObj) => {
if (!sampObj) {
throw new redisErrors.ResourceNotFoundError({
Expand All @@ -754,7 +817,6 @@ module.exports = {
}

currSampObj = sampObj;
const aspectName = sampleName.split('|')[ONE];
return redisOps.getHashPromise(aspectType, aspectName);
})
.then((aspObj) => {
Expand Down
3 changes: 2 additions & 1 deletion cache/sampleStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ function isSampleWritable(aspectModel, aspectName, userName) {
const options = {};
options.where = { name: { $iLike: aspectName } };
return aspectModel.findOne(options)
.then((aspect) => Promise.resolve(aspect.isWritableBy(userName)));
.then((aspect) => aspect ? Promise.resolve(aspect.isWritableBy(userName)) :
Promise.resolve(false));
} // isSampleWritable

module.exports = {
Expand Down
112 changes: 112 additions & 0 deletions tests/cache/models/samples/deleteWithoutPerms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/**
* Copyright (c) 2017, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or
* https://opensource.org/licenses/BSD-3-Clause
*/

/**
* tests/cache/models/samples/deleteWithoutPerms.js
*/
'use strict';

const supertest = require('supertest');
const api = supertest(require('../../../../index').app);
const constants = require('../../../../api/v1/constants');
const tu = require('../../../testUtils');
const u = require('./utils');
const samstoinit = require('../../../../cache/sampleStoreInit');
const rtu = require('../redisTestUtil');
const Sample = tu.db.Sample;
const User = tu.db.User;
const path = '/v1/samples';
const deleteAllRelLinkPath = '/v1/samples/{key}/relatedLinks';
const deleteOneRelLinkPath = '/v1/samples/{key}/relatedLinks/{akey}';

describe('api: DELETE Sample without permission', () => {
let sampleName;
let otherValidToken;
let user;

before((done) => {
tu.toggleOverride('enforceWritePermission', true);
tu.toggleOverride('enableRedisSampleStore', true);
tu.createToken()
.then(() => {
return tu.createUser('myUniqueUser');
})
.then((usr) => {
return tu.createTokenFromUserName(usr.name);
})
.then((tkn) => {
otherValidToken = tkn;
done();
})
.catch((err) => done(err));
});

before((done) => {
User.findOne({ where: { name: tu.userName } })
.then((usr) => {
user = usr;
return u.doSetup();
})
.then((samp) => Sample.create(samp))
.then((samp) => {
sampleName = samp.name;
return samp.getAspect();
})
.then((asp) => asp.addWriters(user))
.then(() => samstoinit.populate())
.then(() => done())
.catch((err) => done(err));
});

after(rtu.forceDelete);
after(() => tu.toggleOverride('enableRedisSampleStore', false));
after(() => tu.toggleOverride('enforceWritePermission', false));


it('deleting sample without permission should return 403', (done) => {
api.delete(`${path}/${sampleName}`)
.set('Authorization', otherValidToken)
.expect(constants.httpStatus.FORBIDDEN)
.end((err /* res */) => {
if (err) {
return done(err);
}
return done();
});
});


it('403 for deleting relatedLinks without permission', (done) => {
api.delete(
deleteOneRelLinkPath.replace('{key}', sampleName)
.replace('{akey}', 'rlink0')
)
.set('Authorization', otherValidToken)
.expect(constants.httpStatus.FORBIDDEN)
.end((err /* res */) => {
if (err) {
return done(err);
}
return done();
});
});

it('403 for deleting all the relatedLinks without permission', (done) => {
api.delete(
deleteAllRelLinkPath.replace('{key}', sampleName)
)
.set('Authorization', otherValidToken)
.expect(constants.httpStatus.FORBIDDEN)
.end((err /* res */) => {
if (err) {
return done(err);
}
return done();
});
});
});

0 comments on commit 9da8928

Please sign in to comment.