Skip to content

Commit 6103784

Browse files
authored
Merge pull request #569 from gets0ul/issue_560
Fix for Issue #560 Don't return members to user without enough permission
2 parents 2cd7ea6 + fc1a929 commit 6103784

File tree

4 files changed

+74
-6
lines changed

4 files changed

+74
-6
lines changed

src/routes/projects/get.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import config from 'config';
33
import { middleware as tcMiddleware } from 'tc-core-library-js';
44
import models from '../../models';
55
import util from '../../util';
6+
import { PERMISSION } from '../../permissions/constants';
67

78
const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
89
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
@@ -103,7 +104,8 @@ const retrieveProjectFromES = (projectId, req) => {
103104
fields = fields ? fields.split(',') : [];
104105
fields = util.parseFields(fields, {
105106
projects: PROJECT_ATTRIBUTES,
106-
project_members: util.addUserDetailsFieldsIfAllowed(PROJECT_MEMBER_ATTRIBUTES_ES, req),
107+
project_members: util.hasPermissionByReq(PERMISSION.READ_PROJECT_MEMBER, req)
108+
? util.addUserDetailsFieldsIfAllowed(PROJECT_MEMBER_ATTRIBUTES_ES, req) : null,
107109
project_member_invites: PROJECT_MEMBER_INVITE_ATTRIBUTES,
108110
project_phases: PROJECT_PHASE_ATTRIBUTES,
109111
project_phases_products: PROJECT_PHASE_PRODUCTS_ATTRIBUTES,
@@ -143,7 +145,9 @@ const retrieveProjectFromDB = (projectId, req) => {
143145
return Promise.reject(apiErr);
144146
}
145147
// check context for project members
146-
project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members));
148+
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_MEMBER, req)) {
149+
project.members = _.map(req.context.currentProjectMembers, m => _.pick(m, fields.project_members));
150+
}
147151
// check if attachments field was requested
148152
if (!req.query.fields || _.indexOf(req.query.fields, 'attachments') > -1) {
149153
return util.getProjectAttachments(req, project.id);

src/routes/projects/get.spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ describe('GET Project', () => {
268268
should.not.exist(resJson.billingAccountId);
269269
should.exist(resJson.name);
270270
resJson.status.should.be.eql('draft');
271-
resJson.members.should.have.lengthOf(2);
271+
should.not.exist(resJson.members);
272272
done();
273273
}
274274
});

src/routes/projects/list.js

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,11 @@ const retrieveProjectsFromDB = (req, criteria, sort, ffields) => {
492492

493493
// make sure project.id is part of fields
494494
if (_.indexOf(fields.projects, 'id') < 0) fields.projects.push('id');
495+
// add userId to project_members field so it can be used to check READ_PROJECT_MEMBER permission below.
496+
const addMembersUserId = fields.project_members.length > 0 && _.indexOf(fields.project_members, 'userId') < 0;
497+
if (addMembersUserId) {
498+
fields.project_members.push('userId');
499+
}
495500
const retrieveAttachments = !req.query.fields || req.query.fields.indexOf('attachments') > -1;
496501
const retrieveMembers = !req.query.fields || !!fields.project_members.length;
497502

@@ -533,7 +538,19 @@ const retrieveProjectsFromDB = (req, criteria, sort, ffields) => {
533538
const p = fp;
534539
// if values length is 1 it could be either attachments or members
535540
if (retrieveMembers) {
536-
p.members = _.filter(allMembers, m => m.projectId === p.id);
541+
const pMembers = _.filter(allMembers, m => m.projectId === p.id);
542+
// check if have permission to read project members
543+
if (util.hasPermission(PERMISSION.READ_PROJECT_MEMBER, req.authUser, pMembers)) {
544+
if (addMembersUserId) {
545+
// remove the userId from the returned members array if it was added before
546+
// as it is only needed for checking permission.
547+
_.forEach(pMembers, (m) => {
548+
const fm = m;
549+
delete fm.userId;
550+
});
551+
}
552+
p.members = pMembers;
553+
}
537554
}
538555
if (retrieveAttachments) {
539556
p.attachments = _.filter(allAttachments, a => a.projectId === p.id);
@@ -562,12 +579,36 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
562579
if (_.indexOf(fields.projects, 'id') < 0) {
563580
fields.projects.push('id');
564581
}
582+
// add userId to project_members field so it can be used to check READ_PROJECT_MEMBER permission below.
583+
const addMembersUserId = fields.project_members.length > 0 && _.indexOf(fields.project_members, 'userId') < 0;
584+
if (addMembersUserId) {
585+
fields.project_members.push('userId');
586+
}
565587

566588
const searchCriteria = parseElasticSearchCriteria(criteria, fields, order) || {};
567589
return new Promise((accept, reject) => {
568590
const es = util.getElasticSearchClient();
569591
es.search(searchCriteria).then((docs) => {
570592
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
593+
if (rows) {
594+
_.forEach(rows, (p) => {
595+
const fp = p;
596+
if (fp.members) {
597+
// check if have permission to read project members
598+
if (!util.hasPermission(PERMISSION.READ_PROJECT_MEMBER, req.authUser, fp.members)) {
599+
delete fp.members;
600+
}
601+
if (fp.members && addMembersUserId) {
602+
// remove the userId from the returned members array if it was added before
603+
// as it is only needed for checking permission.
604+
_.forEach(fp.members, (m) => {
605+
const fm = m;
606+
delete fm.userId;
607+
});
608+
}
609+
}
610+
});
611+
}
571612
accept({ rows, count: docs.hits.total, pageSize: criteria.limit, page: criteria.page });
572613
}).catch(reject);
573614
});

src/routes/projects/list.spec.js

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,29 @@ describe('LIST Project', () => {
405405
});
406406
});
407407

408+
it('should not include the project members using M2M token without "read:project-members" scope', (done) => {
409+
request(server)
410+
.get('/v5/projects')
411+
.set({
412+
Authorization: `Bearer ${testUtil.m2m['read:projects']}`,
413+
})
414+
.expect('Content-Type', /json/)
415+
.expect(200)
416+
.end((err, res) => {
417+
if (err) {
418+
done(err);
419+
} else {
420+
const resJson = res.body;
421+
should.exist(resJson);
422+
resJson.should.have.lengthOf(3);
423+
resJson.forEach((project) => {
424+
should.not.exist(project.members);
425+
});
426+
done();
427+
}
428+
});
429+
});
430+
408431
it('should return the project when project that is in reviewed state in which the copilot is its member or has been invited', (done) => {
409432
request(server)
410433
.get('/v5/projects')
@@ -1163,7 +1186,7 @@ describe('LIST Project', () => {
11631186
request(server)
11641187
.get('/v5/projects/')
11651188
.set({
1166-
Authorization: `Bearer ${testUtil.jwts.member2}`,
1189+
Authorization: `Bearer ${testUtil.jwts.member}`,
11671190
})
11681191
.expect('Content-Type', /json/)
11691192
.expect(200)
@@ -1185,7 +1208,7 @@ describe('LIST Project', () => {
11851208
request(server)
11861209
.get('/v5/projects/?fields=members.email,members.id')
11871210
.set({
1188-
Authorization: `Bearer ${testUtil.jwts.member2}`,
1211+
Authorization: `Bearer ${testUtil.jwts.member}`,
11891212
})
11901213
.expect('Content-Type', /json/)
11911214
.expect(200)

0 commit comments

Comments
 (0)