Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
30 changes: 27 additions & 3 deletions docs/permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -273,10 +273,10 @@ <h2 class="anchor-container">
<div class="row border-top">
<div class="col py-2">
<div class="permission-title anchor-container">
<a href="#UPDATE_PROJECT_DIRECT_PROJECT_ID" name="UPDATE_PROJECT_DIRECT_PROJECT_ID" class="anchor"></a>Update Project property &quot;directProjectId&quot;
<a href="#MANAGE_PROJECT_DIRECT_PROJECT_ID" name="MANAGE_PROJECT_DIRECT_PROJECT_ID" class="anchor"></a>Manage Project property &quot;directProjectId&quot;
</div>
<div class="permission-variable"><small><code>UPDATE_PROJECT_DIRECT_PROJECT_ID</code></small></div>
<div class="text-black-50 small-text"></div>
<div class="permission-variable"><small><code>MANAGE_PROJECT_DIRECT_PROJECT_ID</code></small></div>
<div class="text-black-50 small-text">Who can set or update the &quot;directProjectId&quot; property.</div>
</div>
<div class="col-9 py-2">
<div>
Expand Down Expand Up @@ -332,6 +332,30 @@ <h2 class="anchor-container">
</div>
</div>
</div>
<div class="row border-top">
<div class="col py-2">
<div class="permission-title anchor-container">
<a href="#MANAGE_PROJECT_BILLING_ACCOUNT_ID" name="MANAGE_PROJECT_BILLING_ACCOUNT_ID" class="anchor"></a>Manage Project property &quot;billingAccountId&quot;
</div>
<div class="permission-variable"><small><code>MANAGE_PROJECT_BILLING_ACCOUNT_ID</code></small></div>
<div class="text-black-50 small-text">Who can set or update the &quot;billingAccountId&quot; property.</div>
</div>
<div class="col-9 py-2">
<div>
</div>

<div>
<span class="badge badge-success" title="Allowed Topcoder Role">Connect Manager</span>
<span class="badge badge-success" title="Allowed Topcoder Role">administrator</span>
</div>

<div>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:connect_project</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">all:projects</span>
<span class="badge badge-dark" title="Allowed Topcoder Role">write:projects-billing-accounts</span>
</div>
</div>
</div>
<div class="row">
<div class="col pt-5 pb-2">
<h2 class="anchor-container">
Expand Down
1 change: 1 addition & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ export const M2M_SCOPES = {
ALL: 'all:projects',
READ: 'read:projects',
WRITE: 'write:projects',
WRITE_BILLING_ACCOUNTS: 'write:projects-billing-accounts',
},
PROJECT_MEMBERS: {
ALL: 'all:project-members',
Expand Down
24 changes: 22 additions & 2 deletions src/permissions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ const SCOPES_PROJECTS_WRITE = [
M2M_SCOPES.PROJECTS.WRITE,
];

const SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.PROJECTS.ALL,
M2M_SCOPES.PROJECTS.WRITE_BILLING_ACCOUNTS,
];

const SCOPES_PROJECT_MEMBERS_READ = [
M2M_SCOPES.CONNECT_PROJECT_ADMIN,
M2M_SCOPES.PROJECT_MEMBERS.ALL,
Expand Down Expand Up @@ -142,10 +148,11 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
scopes: SCOPES_PROJECTS_WRITE,
},

UPDATE_PROJECT_DIRECT_PROJECT_ID: {
MANAGE_PROJECT_DIRECT_PROJECT_ID: {
meta: {
title: 'Update Project property "directProjectId"',
title: 'Manage Project property "directProjectId"',
group: 'Project',
description: 'Who can set or update the "directProjectId" property.',
},
topcoderRoles: [
USER_ROLE.MANAGER,
Expand All @@ -172,6 +179,19 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
scopes: SCOPES_PROJECTS_WRITE,
},

MANAGE_PROJECT_BILLING_ACCOUNT_ID: {
meta: {
title: 'Manage Project property "billingAccountId"',
group: 'Project',
description: 'Who can set or update the "billingAccountId" property.',
},
topcoderRoles: [
USER_ROLE.MANAGER,
USER_ROLE.TOPCODER_ADMIN,
],
scopes: SCOPES_PROJECTS_WRITE_BILLING_ACCOUNTS,
},

/*
* Project Member
*/
Expand Down
12 changes: 12 additions & 0 deletions src/routes/projects/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,18 @@ module.exports = [
*/
(req, res, next) => {
const project = req.body;
if (_.has(project, 'directProjectId') &&
!util.hasPermissionByReq(PERMISSION.MANAGE_PROJECT_DIRECT_PROJECT_ID, req)) {
const err = new Error('You do not have permission to set \'directProjectId\' property');
err.status = 400;
throw err;
}
if (_.has(project, 'billingAccountId') &&
!util.hasPermissionByReq(PERMISSION.MANAGE_PROJECT_BILLING_ACCOUNT_ID, req)) {
const err = new Error('You do not have permission to set \'billingAccountId\' property');
err.status = 400;
throw err;
}
// by default connect admin and managers joins projects as manager
const userRole = util.hasPermissionByReq(PERMISSION.CREATE_PROJECT_AS_MANAGER, req)
? PROJECT_MEMBER_ROLE.MANAGER
Expand Down
44 changes: 35 additions & 9 deletions src/routes/projects/create.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,6 @@ describe('Project create', () => {
type: 'generic',
description: 'test project',
details: {},
billingAccountId: 1,
name: 'test project1',
bookmarks: [{
title: 'title1',
Expand All @@ -277,7 +276,6 @@ describe('Project create', () => {
type: 'generic',
description: 'test project',
details: {},
billingAccountId: 1,
name: 'test project1',
attachments: [
{
Expand Down Expand Up @@ -399,6 +397,34 @@ describe('Project create', () => {
.expect(400, done);
});

it(`should return 400 when creating project with billingAccountId
without "write:projects-billing-accounts" scope in M2M token`, (done) => {
const validBody = _.cloneDeep(body);
validBody.billingAccountId = 1;
request(server)
.post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.m2m['write:projects']}`,
})
.send(validBody)
.expect('Content-Type', /json/)
.expect(400, done);
});

it(`should return 400 when creating project with directProjectId
without "write:projects" scope in M2M token`, (done) => {
const validBody = _.cloneDeep(body);
validBody.directProjectId = 1;
request(server)
.post('/v5/projects')
.set({
Authorization: `Bearer ${testUtil.m2m['write:project-members']}`,
})
.send(validBody)
.expect('Content-Type', /json/)
.expect(400, done);
});

it('should return 201 if valid user and data', (done) => {
const validBody = _.cloneDeep(body);
validBody.templateId = 3;
Expand Down Expand Up @@ -433,7 +459,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down Expand Up @@ -489,7 +515,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down Expand Up @@ -544,7 +570,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down Expand Up @@ -598,7 +624,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(bodyWithAttachments.type);
Expand Down Expand Up @@ -679,7 +705,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down Expand Up @@ -752,7 +778,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down Expand Up @@ -885,7 +911,7 @@ describe('Project create', () => {
} else {
const resJson = res.body;
should.exist(resJson);
should.exist(resJson.billingAccountId);
should.not.exist(resJson.billingAccountId);
should.exist(resJson.name);
resJson.status.should.be.eql('in_review');
resJson.type.should.be.eql(body.type);
Expand Down
8 changes: 6 additions & 2 deletions src/routes/projects/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,12 @@ const validateUpdates = (existingProject, updatedProps, req) => {
// }
}
if (_.has(updatedProps, 'directProjectId') &&
!util.hasPermissionByReq(PERMISSION.UPDATE_PROJECT_DIRECT_PROJECT_ID, req)) {
errors.push('Don\'t have permission to update \'directProjectId\' property');
!util.hasPermissionByReq(PERMISSION.MANAGE_PROJECT_DIRECT_PROJECT_ID, req)) {
errors.push('You do not have permission to update \'directProjectId\' property');
}
if (_.has(updatedProps, 'billingAccountId') &&
!util.hasPermissionByReq(PERMISSION.MANAGE_PROJECT_BILLING_ACCOUNT_ID, req)) {
errors.push('You do not have permission to update \'billingAccountId\' property');
}
if ((existingProject.status !== PROJECT_STATUS.DRAFT) && (updatedProps.status === PROJECT_STATUS.DRAFT)) {
errors.push('cannot update a project status to draft');
Expand Down
20 changes: 17 additions & 3 deletions src/routes/projects/update.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ describe('Project', () => {
request(server)
.patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
billingAccountId: 123,
Expand All @@ -600,7 +600,7 @@ describe('Project', () => {
should.exist(resJson);
resJson.billingAccountId.should.equal(123);
resJson.updatedAt.should.not.equal('2016-06-30 00:33:07+00');
resJson.updatedBy.should.equal(40051332);
resJson.updatedBy.should.equal(40051334);
server.services.pubsub.publish.calledWith('project.updated').should.be.true;
done();
}
Expand All @@ -611,7 +611,7 @@ describe('Project', () => {
request(server)
.patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.jwts.copilot}`,
Authorization: `Bearer ${testUtil.jwts.manager}`,
})
.send({
billingAccountId: 1,
Expand Down Expand Up @@ -659,6 +659,20 @@ describe('Project', () => {
});
});

it('should return 400 when updating billingAccountId without "write:projects-billing-accounts" scope in M2M token',
(done) => {
request(server)
.patch(`/v5/projects/${project1.id}`)
.set({
Authorization: `Bearer ${testUtil.m2m[M2M_SCOPES.PROJECTS.WRITE]}`,
})
.send({
billingAccountId: 123,
})
.expect('Content-Type', /json/)
.expect(400, done);
});

it.skip('should return 200 and update bookmarks', (done) => {
request(server)
.patch(`/v5/projects/${project1.id}`)
Expand Down