Skip to content

Fix for Issue #561 Return empty invites for user without enough permission #570

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions docs/permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -636,8 +636,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-invites</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -670,8 +670,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">read:project-invites</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -704,8 +704,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -734,8 +734,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -759,8 +759,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -782,8 +782,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -806,8 +806,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -831,8 +831,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -854,8 +854,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -879,8 +879,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand Down Expand Up @@ -909,8 +909,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand All @@ -934,8 +934,8 @@ <h2 class="anchor-container">

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-members</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:project-invites</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:project-invites</span>
</div>
</div>
</div>
Expand Down
5 changes: 5 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@ export const M2M_SCOPES = {
READ: 'read:project-members',
WRITE: 'write:project-members',
},
PROJECT_INVITES: {
ALL: 'all:project-invites',
READ: 'read:project-invites',
WRITE: 'write:project-invites',
},
};

export const TIMELINE_REFERENCES = {
Expand Down
36 changes: 24 additions & 12 deletions src/permissions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ const SCOPES_PROJECT_MEMBERS_WRITE = [
M2M_SCOPES.PROJECT_MEMBERS.WRITE,
];

const SCOPES_PROJECT_INVITES_READ = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.PROJECT_INVITES.ALL,
M2M_SCOPES.PROJECT_INVITES.READ,
];

const SCOPES_PROJECT_INVITES_WRITE = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.PROJECT_INVITES.ALL,
M2M_SCOPES.PROJECT_INVITES.WRITE,
];

export const PERMISSION = { // eslint-disable-line import/prefer-default-export
/*
* Project
Expand Down Expand Up @@ -303,7 +315,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
description: 'Who can view own invite.',
},
topcoderRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_READ,
scopes: SCOPES_PROJECT_INVITES_READ,
},

READ_PROJECT_INVITE_NOT_OWN: {
Expand All @@ -314,7 +326,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
},
topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS,
projectRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_READ,
scopes: SCOPES_PROJECT_INVITES_READ,
},

CREATE_PROJECT_INVITE_CUSTOMER: {
Expand All @@ -325,7 +337,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
},
topcoderRoles: TOPCODER_ROLES_MANAGERS_AND_ADMINS,
projectRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

CREATE_PROJECT_INVITE_NON_CUSTOMER: {
Expand All @@ -336,7 +348,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
},
topcoderRoles: TOPCODER_ROLES_ADMINS,
projectRoles: PROJECT_ROLES_MANAGEMENT,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

CREATE_PROJECT_INVITE_COPILOT_DIRECTLY: {
Expand All @@ -349,7 +361,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
...TOPCODER_ROLES_ADMINS,
USER_ROLE.COPILOT_MANAGER,
],
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

UPDATE_PROJECT_INVITE_OWN: {
Expand All @@ -359,7 +371,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
description: 'Who can update own invite.',
},
topcoderRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

UPDATE_PROJECT_INVITE_NOT_OWN: {
Expand All @@ -369,7 +381,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
description: 'Who can update invites for other members.',
},
topcoderRoles: TOPCODER_ROLES_ADMINS,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

UPDATE_PROJECT_INVITE_REQUESTED: {
Expand All @@ -382,7 +394,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
...TOPCODER_ROLES_ADMINS,
USER_ROLE.COPILOT_MANAGER,
],
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

DELETE_PROJECT_INVITE_OWN: {
Expand All @@ -392,7 +404,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
description: 'Who can delete own invite.',
},
topcoderRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

DELETE_PROJECT_INVITE_NOT_OWN_CUSTOMER: {
Expand All @@ -403,7 +415,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
},
topcoderRoles: TOPCODER_ROLES_ADMINS,
projectRoles: ALL,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

DELETE_PROJECT_INVITE_NOT_OWN_NON_CUSTOMER: {
Expand All @@ -414,7 +426,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
},
topcoderRoles: TOPCODER_ROLES_ADMINS,
projectRoles: PROJECT_ROLES_MANAGEMENT,
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

DELETE_PROJECT_INVITE_REQUESTED: {
Expand All @@ -427,7 +439,7 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
...TOPCODER_ROLES_ADMINS,
USER_ROLE.COPILOT_MANAGER,
],
scopes: SCOPES_PROJECT_MEMBERS_WRITE,
scopes: SCOPES_PROJECT_INVITES_WRITE,
},

/**
Expand Down
34 changes: 32 additions & 2 deletions src/routes/projects/get.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import config from 'config';
import { middleware as tcMiddleware } from 'tc-core-library-js';
import models from '../../models';
import util from '../../util';
import { PERMISSION } from '../../permissions/constants';

const ES_PROJECT_INDEX = config.get('elasticsearchConfig.indexName');
const ES_PROJECT_TYPE = config.get('elasticsearchConfig.docType');
Expand Down Expand Up @@ -115,7 +116,26 @@ const retrieveProjectFromES = (projectId, req) => {
const es = util.getElasticSearchClient();
es.search(searchCriteria).then((docs) => {
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
accept(rows[0]);
const project = rows[0];
if (project && project.invites) {
if (!util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
let invites;
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
// only include own invites
const currentUserId = req.authUser.userId;
const currentUserEmail = req.authUser.email;
invites = _.filter(project.invites, invite => (
(invite.userId !== null && invite.userId === currentUserId) ||
(invite.email && currentUserEmail && invite.email.toLowerCase() === currentUserEmail.toLowerCase())
));
} else {
// return empty invites
invites = [];
}
_.set(project, 'invites', invites);
}
}
accept(project);
}).catch(reject);
});
};
Expand Down Expand Up @@ -156,7 +176,17 @@ const retrieveProjectFromDB = (projectId, req) => {
if (attachments) {
project.attachments = attachments;
}
return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
// include all invites
return models.ProjectMemberInvite.getPendingAndReguestedInvitesForProject(projectId);
} else if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
// include only own invites
const currentUserId = req.authUser.userId;
const email = req.authUser.email;
return models.ProjectMemberInvite.getPendingOrRequestedProjectInvitesForUser(projectId, email, currentUserId);
}
// empty
return Promise.resolve([]);
})
.then((invites) => {
project.invites = invites;
Expand Down
20 changes: 20 additions & 0 deletions src/routes/projects/get.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,26 @@ describe('GET Project', () => {
});
});

it('should return the project with empty invites using M2M token without "read:project-invites" scope', (done) => {
request(server)
.get(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.m2m['read:projects']}`,
})
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
if (err) {
done(err);
} else {
const resJson = res.body;
should.exist(resJson);
resJson.invites.should.be.empty;
done();
}
});
});

it('should return project with "members", "invites", and "attachments" by default when data comes from ES', (done) => {
request(server)
.get(`/v5/projects/${data[0].id}`)
Expand Down
21 changes: 21 additions & 0 deletions src/routes/projects/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,27 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
const es = util.getElasticSearchClient();
es.search(searchCriteria).then((docs) => {
const rows = _.map(docs.hits.hits, single => single._source); // eslint-disable-line no-underscore-dangle
if (rows) {
if (!util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_NOT_OWN, req)) {
if (util.hasPermissionByReq(PERMISSION.READ_PROJECT_INVITE_OWN, req)) {
// only include own invites
const currentUserId = req.authUser.userId;
const email = req.authUser.email;
_.forEach(rows, (fp) => {
const invites = _.filter(fp.invites, invite => (
(invite.userId !== null && invite.userId === currentUserId) ||
(invite.email && currentUserEmail && invite.email.toLowerCase() === currentUserEmail.toLowerCase())
));
_.set(fp, 'invites', invites);
});
} else {
// return empty invites
_.forEach(rows, (fp) => {
_.set(fp, 'invites', []);
});
}
}
}
accept({ rows, count: docs.hits.total, pageSize: criteria.limit, page: criteria.page });
}).catch(reject);
});
Expand Down
Loading