Skip to content

Commit 613beeb

Browse files
committed
- use separate scope for invites
- return project with empty invites if user doesn't have enough permission
1 parent 2cd7ea6 commit 613beeb

File tree

7 files changed

+145
-40
lines changed

7 files changed

+145
-40
lines changed

docs/permissions.html

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -636,8 +636,8 @@ <h2 class="anchor-container">
636636

637637
<div>
638638
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
639-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
640-
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-members</span>
639+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
640+
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-invites</span>
641641
</div>
642642
</div>
643643
</div>
@@ -670,8 +670,8 @@ <h2 class="anchor-container">
670670

671671
<div>
672672
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
673-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
674-
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-members</span>
673+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
674+
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-invites</span>
675675
</div>
676676
</div>
677677
</div>
@@ -704,8 +704,8 @@ <h2 class="anchor-container">
704704

705705
<div>
706706
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
707-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
708-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
707+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
708+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
709709
</div>
710710
</div>
711711
</div>
@@ -734,8 +734,8 @@ <h2 class="anchor-container">
734734

735735
<div>
736736
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
737-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
738-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
737+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
738+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
739739
</div>
740740
</div>
741741
</div>
@@ -759,8 +759,8 @@ <h2 class="anchor-container">
759759

760760
<div>
761761
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
762-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
763-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
762+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
763+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
764764
</div>
765765
</div>
766766
</div>
@@ -782,8 +782,8 @@ <h2 class="anchor-container">
782782

783783
<div>
784784
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
785-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
786-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
785+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
786+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
787787
</div>
788788
</div>
789789
</div>
@@ -806,8 +806,8 @@ <h2 class="anchor-container">
806806

807807
<div>
808808
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
809-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
810-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
809+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
810+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
811811
</div>
812812
</div>
813813
</div>
@@ -831,8 +831,8 @@ <h2 class="anchor-container">
831831

832832
<div>
833833
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
834-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
835-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
834+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
835+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
836836
</div>
837837
</div>
838838
</div>
@@ -854,8 +854,8 @@ <h2 class="anchor-container">
854854

855855
<div>
856856
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
857-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
858-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
857+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
858+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
859859
</div>
860860
</div>
861861
</div>
@@ -879,8 +879,8 @@ <h2 class="anchor-container">
879879

880880
<div>
881881
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
882-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
883-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
882+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
883+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
884884
</div>
885885
</div>
886886
</div>
@@ -909,8 +909,8 @@ <h2 class="anchor-container">
909909

910910
<div>
911911
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
912-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
913-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
912+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
913+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
914914
</div>
915915
</div>
916916
</div>
@@ -934,8 +934,8 @@ <h2 class="anchor-container">
934934

935935
<div>
936936
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
937-
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
938-
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
937+
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
938+
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
939939
</div>
940940
</div>
941941
</div>

src/constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,11 @@ export const M2M_SCOPES = {
281281
READ: 'read:project-members',
282282
WRITE: 'write:project-members',
283283
},
284+
PROJECT_INVITES: {
285+
ALL: 'all:project-invites',
286+
READ: 'read:project-invites',
287+
WRITE: 'write:project-invites',
288+
},
284289
};
285290

286291
export const TIMELINE_REFERENCES = {

src/permissions/constants.js

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,18 @@ const SCOPES_PROJECT_MEMBERS_WRITE = [
9191
M2M_SCOPES.PROJECT_MEMBERS.WRITE,
9292
];
9393

94+
const SCOPES_PROJECT_INVITES_READ = [
95+
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
96+
M2M_SCOPES.PROJECT_INVITES.ALL,
97+
M2M_SCOPES.PROJECT_INVITES.READ,
98+
];
99+
100+
const SCOPES_PROJECT_INVITES_WRITE = [
101+
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
102+
M2M_SCOPES.PROJECT_INVITES.ALL,
103+
M2M_SCOPES.PROJECT_INVITES.WRITE,
104+
];
105+
94106
export const PERMISSION = { // eslint-disable-line import/prefer-default-export
95107
/*
96108
* Project
@@ -303,7 +315,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
303315
description: 'Who can view own invite.',
304316
},
305317
topcoderRoles: ALL,
306-
scopes: SCOPES_PROJECT_MEMBERS_READ,
318+
scopes: SCOPES_PROJECT_INVITES_READ,
307319
},
308320

309321
READ_PROJECT_INVITE_NOT_OWN: {
@@ -314,7 +326,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
314326
},
315327
topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS,
316328
projectRoles: ALL,
317-
scopes: SCOPES_PROJECT_MEMBERS_READ,
329+
scopes: SCOPES_PROJECT_INVITES_READ,
318330
},
319331

320332
CREATE_PROJECT_INVITE_CUSTOMER: {
@@ -325,7 +337,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
325337
},
326338
topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS,
327339
projectRoles: ALL,
328-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
340+
scopes: SCOPES_PROJECT_INVITES_WRITE,
329341
},
330342

331343
CREATE_PROJECT_INVITE_NON_CUSTOMER: {
@@ -336,7 +348,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
336348
},
337349
topcoderRoles: TOPCODER_ROLES_ADMINS,
338350
projectRoles: PROJECT_ROLES_MANAGEMENT,
339-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
351+
scopes: SCOPES_PROJECT_INVITES_WRITE,
340352
},
341353

342354
CREATE_PROJECT_INVITE_COPILOT_DIRECTLY: {
@@ -349,7 +361,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
349361
...TOPCODER_ROLES_ADMINS,
350362
USER_ROLE.COPILOT_MANAGER,
351363
],
352-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
364+
scopes: SCOPES_PROJECT_INVITES_WRITE,
353365
},
354366

355367
UPDATE_PROJECT_INVITE_OWN: {
@@ -359,7 +371,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
359371
description: 'Who can update own invite.',
360372
},
361373
topcoderRoles: ALL,
362-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
374+
scopes: SCOPES_PROJECT_INVITES_WRITE,
363375
},
364376

365377
UPDATE_PROJECT_INVITE_NOT_OWN: {
@@ -369,7 +381,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
369381
description: 'Who can update invites for other members.',
370382
},
371383
topcoderRoles: TOPCODER_ROLES_ADMINS,
372-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
384+
scopes: SCOPES_PROJECT_INVITES_WRITE,
373385
},
374386

375387
UPDATE_PROJECT_INVITE_REQUESTED: {
@@ -382,7 +394,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
382394
...TOPCODER_ROLES_ADMINS,
383395
USER_ROLE.COPILOT_MANAGER,
384396
],
385-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
397+
scopes: SCOPES_PROJECT_INVITES_WRITE,
386398
},
387399

388400
DELETE_PROJECT_INVITE_OWN: {
@@ -392,7 +404,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
392404
description: 'Who can delete own invite.',
393405
},
394406
topcoderRoles: ALL,
395-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
407+
scopes: SCOPES_PROJECT_INVITES_WRITE,
396408
},
397409

398410
DELETE_PROJECT_INVITE_NOT_OWN_CUSTOMER: {
@@ -403,7 +415,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
403415
},
404416
topcoderRoles: TOPCODER_ROLES_ADMINS,
405417
projectRoles: ALL,
406-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
418+
scopes: SCOPES_PROJECT_INVITES_WRITE,
407419
},
408420

409421
DELETE_PROJECT_INVITE_NOT_OWN_NON_CUSTOMER: {
@@ -414,7 +426,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
414426
},
415427
topcoderRoles: TOPCODER_ROLES_ADMINS,
416428
projectRoles: PROJECT_ROLES_MANAGEMENT,
417-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
429+
scopes: SCOPES_PROJECT_INVITES_WRITE,
418430
},
419431

420432
DELETE_PROJECT_INVITE_REQUESTED: {
@@ -427,7 +439,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
427439
...TOPCODER_ROLES_ADMINS,
428440
USER_ROLE.COPILOT_MANAGER,
429441
],
430-
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
442+
scopes: SCOPES_PROJECT_INVITES_WRITE,
431443
},
432444

433445
/**

src/routes/projects/get.js

Lines changed: 29 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');
@@ -115,7 +116,23 @@ const retrieveProjectFromES = (projectId, req) => {
115116
const es = util.getElasticSearchClient();
116117
es.search(searchCriteria).then((docs) => {
117118
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
118-
accept(rows[0]);
119+
const project = rows[0];
120+
if (project && project.invites) {
121+
if (!util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
122+
let invites;
123+
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
124+
// only include own invites
125+
const currentUserId = req.authUser.userId;
126+
const email = req.authUser.email;
127+
invites = _.filter(project.invites, invite => invite.userId === currentUserId || invite.email === email);
128+
} else {
129+
// return empty invites
130+
invites = [];
131+
}
132+
_.set(project, 'invites', invites);
133+
}
134+
}
135+
accept(project);
119136
}).catch(reject);
120137
});
121138
};
@@ -156,7 +173,17 @@ const retrieveProjectFromDB = (projectId, req) => {
156173
if (attachments) {
157174
project.attachments = attachments;
158175
}
159-
return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
176+
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
177+
// include all invites
178+
return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
179+
} else if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
180+
// include only own invites
181+
const currentUserId = req.authUser.userId;
182+
const email = req.authUser.email;
183+
return models.ProjectMemberInvite.getPendingOrRequestedProjectInvitesForUser(projectId, email, currentUserId);
184+
}
185+
// empty
186+
return Promise.resolve([]);
160187
})
161188
.then((invites) => {
162189
project.invites = invites;

src/routes/projects/get.spec.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,26 @@ describe('GET Project', () => {
274274
});
275275
});
276276

277+
it('should return the project with empty invites using M2M token without "read:project-invites" scope', (done) => {
278+
request(server)
279+
.get(`/v5/projects/${project1.id}`)
280+
.set({
281+
Authorization: `Bearer ${testUtil.m2m['read:projects']}`,
282+
})
283+
.expect('Content-Type', /json/)
284+
.expect(200)
285+
.end((err, res) => {
286+
if (err) {
287+
done(err);
288+
} else {
289+
const resJson = res.body;
290+
should.exist(resJson);
291+
resJson.invites.should.be.empty;
292+
done();
293+
}
294+
});
295+
});
296+
277297
it('should return project with "members", "invites", and "attachments" by default when data comes from ES', (done) => {
278298
request(server)
279299
.get(`/v5/projects/${data[0].id}`)

src/routes/projects/list.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,24 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
568568
const es = util.getElasticSearchClient();
569569
es.search(searchCriteria).then((docs) => {
570570
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
571+
if (rows) {
572+
if (!util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
573+
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
574+
// only include own invites
575+
const currentUserId = req.authUser.userId;
576+
const email = req.authUser.email;
577+
_.forEach(rows, (fp) => {
578+
const invites = _.filter(fp.invites, invite => invite.userId === currentUserId || invite.email === email);
579+
_.set(fp, 'invites', invites);
580+
});
581+
} else {
582+
// return empty invites
583+
_.forEach(rows, (fp) => {
584+
_.set(fp, 'invites', []);
585+
});
586+
}
587+
}
588+
}
571589
accept({ rows, count: docs.hits.total, pageSize: criteria.limit, page: criteria.page });
572590
}).catch(reject);
573591
});

0 commit comments

Comments
 (0)