diff --git a/docs/permissions.html b/docs/permissions.html
index f1ed60d8..97f5d5f5 100644
--- a/docs/permissions.html
+++ b/docs/permissions.html
@@ -636,8 +636,8 @@
all:connect_project
- all:project-members
- read:project-members
+ all:project-invites
+ read:project-invites
@@ -670,8 +670,8 @@
all:connect_project
- all:project-members
- read:project-members
+ all:project-invites
+ read:project-invites
@@ -704,8 +704,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -734,8 +734,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -759,8 +759,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -782,8 +782,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -806,8 +806,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -831,8 +831,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -854,8 +854,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -879,8 +879,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -909,8 +909,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
@@ -934,8 +934,8 @@
all:connect_project
- all:project-members
- write:project-members
+ all:project-invites
+ write:project-invites
diff --git a/src/constants.js b/src/constants.js
index 1babbe14..180bf7ea 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -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 = {
diff --git a/src/permissions/constants.js b/src/permissions/constants.js
index d89d8277..77b55b46 100644
--- a/src/permissions/constants.js
+++ b/src/permissions/constants.js
@@ -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
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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,
},
/**
diff --git a/src/routes/projects/get.js b/src/routes/projects/get.js
index caa04531..e7969646 100644
--- a/src/routes/projects/get.js
+++ b/src/routes/projects/get.js
@@ -117,7 +117,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);
});
};
@@ -160,7 +179,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;
diff --git a/src/routes/projects/get.spec.js b/src/routes/projects/get.spec.js
index 3faa8a0e..ed09ffb4 100644
--- a/src/routes/projects/get.spec.js
+++ b/src/routes/projects/get.spec.js
@@ -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}`)
diff --git a/src/routes/projects/list.js b/src/routes/projects/list.js
index 3998e688..8264b493 100755
--- a/src/routes/projects/list.js
+++ b/src/routes/projects/list.js
@@ -591,6 +591,25 @@ const retrieveProjects = (req, criteria, sort, ffields) => {
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 currentUserEmail = 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', []);
+ });
+ }
+ }
_.forEach(rows, (p) => {
const fp = p;
if (fp.members) {
diff --git a/src/routes/projects/list.spec.js b/src/routes/projects/list.spec.js
index f57ecdb3..eb348e1e 100644
--- a/src/routes/projects/list.spec.js
+++ b/src/routes/projects/list.spec.js
@@ -404,6 +404,29 @@ describe('LIST Project', () => {
}
});
});
+
+ it('should return the project with empty invites using M2M token without "read:project-invites" scope', (done) => {
+ request(server)
+ .get('/v5/projects')
+ .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.should.have.lengthOf(3);
+ resJson.forEach((project) => {
+ project.invites.should.be.empty;
+ });
+ done();
+ }
+ });
+ });
it('should not include the project members using M2M token without "read:project-members" scope', (done) => {
request(server)
@@ -1153,9 +1176,9 @@ describe('LIST Project', () => {
should.exist(resJson);
resJson.should.have.lengthOf(1);
resJson[0].name.should.equal('test1');
- resJson[0].invites.should.have.lengthOf(2);
+ resJson[0].invites.should.have.lengthOf(1);
resJson[0].invites[0].should.have.property('email');
- resJson[0].invites[1].email.should.equal('h***o@w***d.com');
+ resJson[0].invites[0].userId.should.equal(40051335);
done();
}
});