Skip to content

New user roles #387

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
merged 7 commits into from
Oct 1, 2019
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
21 changes: 21 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,20 @@ export const PROJECT_MEMBER_ROLE = {
CUSTOMER: 'customer',
COPILOT: 'copilot',
ACCOUNT_MANAGER: 'account_manager',
PROGRAM_MANAGER: 'program_manager',
ACCOUNT_EXECUTIVE: 'account_executive',
SOLUTION_ARCHITECT: 'solution_architect',
PROJECT_MANAGER: 'project_manager',
};

export const PROJECT_MEMBER_MANAGER_ROLES = [
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.OBSERVER,
PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
];

export const USER_ROLE = {
Expand All @@ -42,6 +50,12 @@ export const USER_ROLE = {
COPILOT: 'Connect Copilot',
CONNECT_ADMIN: 'Connect Admin',
COPILOT_MANAGER: 'Connect Copilot Manager',
BUSINESS_DEVELOPMENT_REPRESENTATIVE: 'Business Development Representative',
PRESALES: 'Presales',
ACCOUNT_EXECUTIVE: 'Account Executive',
PROGRAM_MANAGER: 'Program Manager',
SOLUTION_ARCHITECT: 'Solution Architect',
PROJECT_MANAGER: 'Project Manager',
};

export const ADMIN_ROLES = [USER_ROLE.CONNECT_ADMIN, USER_ROLE.TOPCODER_ADMIN];
Expand All @@ -51,6 +65,13 @@ export const MANAGER_ROLES = [
USER_ROLE.MANAGER,
USER_ROLE.TOPCODER_ACCOUNT_MANAGER,
USER_ROLE.COPILOT_MANAGER,
USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE,
USER_ROLE.PRESALES,
USER_ROLE.ACCOUNT_EXECUTIVE,

USER_ROLE.PROGRAM_MANAGER,
USER_ROLE.SOLUTION_ARCHITECT,
USER_ROLE.PROJECT_MANAGER,
];

export const EVENT = {
Expand Down
21 changes: 11 additions & 10 deletions src/events/busApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,17 @@ module.exports = (app, logger) => {
logger.debug('receive PROJECT_MEMBER_ADDED event');

let eventType;
switch (member.role) {
case PROJECT_MEMBER_ROLE.MANAGER:
eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER;
break;
case PROJECT_MEMBER_ROLE.COPILOT:
eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT;
break;
default:
eventType = BUS_API_EVENT.MEMBER_JOINED;
break;
if ([
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
].includes(member.role)) {
eventType = BUS_API_EVENT.MEMBER_JOINED_MANAGER;
} else if (member.role === PROJECT_MEMBER_ROLE.COPILOT) {
eventType = BUS_API_EVENT.MEMBER_JOINED_COPILOT;
} else {
eventType = BUS_API_EVENT.MEMBER_JOINED;
}
const projectId = _.parseInt(req.params.projectId);

Expand Down
16 changes: 14 additions & 2 deletions src/events/projectMembers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,13 @@ const projectMemberAddedHandler = Promise.coroutine(function* a(logger, msg, cha
const newMember = JSON.parse(msg.content.toString());
const projectId = newMember.projectId;
const directUpdatePromise = Promise.coroutine(function* () { // eslint-disable-line func-names
if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], newMember.role) > -1) {
if (_.indexOf([
PROJECT_MEMBER_ROLE.COPILOT,
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
], newMember.role) > -1) {
// add copilot/update manager permissions operation promise
const directProjectId = yield models.Project.getDirectProjectId(projectId);
if (directProjectId) {
Expand Down Expand Up @@ -116,7 +122,13 @@ const projectMemberRemovedHandler = Promise.coroutine(function* (logger, msg, ch
const projectId = member.projectId;
// remove copilot/manager operation promise
const updateDirectProjectPromise = Promise.coroutine(function* () { // eslint-disable-line func-names
if (_.indexOf([PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.MANAGER], member.role) > -1) {
if (_.indexOf([
PROJECT_MEMBER_ROLE.COPILOT,
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
], member.role) > -1) {
const directProjectId = yield models.Project.getDirectProjectId(projectId);
if (directProjectId) {
const token = yield util.getM2MToken();
Expand Down
3 changes: 3 additions & 0 deletions src/permissions/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ export const PERMISSION = { // eslint-disable-line import/prefer-default-export
ROLES_COPILOT_AND_ABOVE: {
topcoderRoles: ADMIN_ROLES,
projectRoles: [
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.COPILOT,
],
Expand Down
8 changes: 7 additions & 1 deletion src/permissions/project.delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ module.exports = freq => new Promise((resolve, reject) => {
const hasAccess = util.hasAdminRole(req) ||
!_.isUndefined(_.find(members, m => m.userId === req.authUser.userId &&
((m.role === PROJECT_MEMBER_ROLE.CUSTOMER && m.isPrimary) ||
m.role === PROJECT_MEMBER_ROLE.MANAGER)));
[
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
].includes(m.role)
)));

if (!hasAccess) {
// user is not an admin nor is a registered project member
Expand Down
7 changes: 6 additions & 1 deletion src/permissions/projectMember.delete.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ module.exports = freq => new Promise((resolve, reject) => {
const memberToBeRemoved = _.find(members, m => m.id === prjMemberId);
// check if auth user has acecss to this project
const hasAccess = util.hasAdminRole(req)
|| (authMember && memberToBeRemoved && (authMember.role === PROJECT_MEMBER_ROLE.MANAGER ||
|| (authMember && memberToBeRemoved && ([
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
].includes(authMember.role) ||
(authMember.role === PROJECT_MEMBER_ROLE.CUSTOMER && authMember.isPrimary &&
memberToBeRemoved.role === PROJECT_MEMBER_ROLE.CUSTOMER) ||
memberToBeRemoved.userId === req.authUser.userId));
Expand Down
2 changes: 1 addition & 1 deletion src/routes/projectMemberInvites/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ module.exports = [
}
return Promise.all(promises).then((rolesList) => {
if (!!invite.userIds && _.includes(PROJECT_MEMBER_MANAGER_ROLES, invite.role)) {
req.log.debug('Chekcing if userId is allowed as manager');
req.log.debug('Checking if userId is allowed as manager');
const forbidUserList = [];
_.zip(invite.userIds, rolesList).forEach((data) => {
const [userId, roles] = data;
Expand Down
68 changes: 64 additions & 4 deletions src/routes/projectMembers/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@ const createProjectMemberValidations = {
param: Joi.object()
.keys({
role: Joi.any()
.valid(PROJECT_MEMBER_ROLE.MANAGER, PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT),
.valid(
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
PROJECT_MEMBER_ROLE.COPILOT,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
),
}),
},
};
Expand All @@ -39,9 +47,49 @@ module.exports = [
return next(err);
}

if (PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT === targetRole &&
!util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) {
const err = new Error(`Only solution architect is able to join as ${targetRole}`);
err.status = 401;
return next(err);
}

if (PROJECT_MEMBER_ROLE.PROJECT_MANAGER === targetRole &&
!util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) {
const err = new Error(`Only project manager is able to join as ${targetRole}`);
err.status = 401;
return next(err);
}

if (PROJECT_MEMBER_ROLE.PROGRAM_MANAGER === targetRole &&
!util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) {
const err = new Error(`Only program manager is able to join as ${targetRole}`);
err.status = 401;
return next(err);
}

if (PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE === targetRole &&
!util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) {
const err = new Error(`Only account executive is able to join as ${targetRole}`);
err.status = 401;
return next(err);
}

if (PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER === targetRole &&
!util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) {
const err = new Error(`Only manager or account manager is able to join as ${targetRole}`);
!util.hasRoles(req, [
USER_ROLE.MANAGER,
USER_ROLE.TOPCODER_ACCOUNT_MANAGER,
USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE,
USER_ROLE.PRESALES,
USER_ROLE.ACCOUNT_EXECUTIVE,
USER_ROLE.PROGRAM_MANAGER,
USER_ROLE.SOLUTION_ARCHITECT,
USER_ROLE.PROJECT_MANAGER,
])) {
const err = new Error(
// eslint-disable-next-line max-len
`Only manager, account manager, business development representative, account executive, program manager, project manager, solution architect, or presales are able to join as ${targetRole}`,
);
err.status = 401;
return next(err);
}
Expand All @@ -53,10 +101,22 @@ module.exports = [
}
} else if (util.hasRoles(req, [USER_ROLE.MANAGER, USER_ROLE.CONNECT_ADMIN])) {
targetRole = PROJECT_MEMBER_ROLE.MANAGER;
} else if (util.hasRoles(req, [USER_ROLE.TOPCODER_ACCOUNT_MANAGER])) {
} else if (util.hasRoles(req, [
USER_ROLE.TOPCODER_ACCOUNT_MANAGER,
USER_ROLE.BUSINESS_DEVELOPMENT_REPRESENTATIVE,
USER_ROLE.PRESALES,
])) {
targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER;
} else if (util.hasRoles(req, [USER_ROLE.COPILOT, USER_ROLE.CONNECT_ADMIN])) {
targetRole = PROJECT_MEMBER_ROLE.COPILOT;
} else if (util.hasRoles(req, [USER_ROLE.ACCOUNT_EXECUTIVE])) {
targetRole = PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE;
} else if (util.hasRoles(req, [USER_ROLE.PROGRAM_MANAGER])) {
targetRole = PROJECT_MEMBER_ROLE.PROGRAM_MANAGER;
} else if (util.hasRoles(req, [USER_ROLE.SOLUTION_ARCHITECT])) {
targetRole = PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT;
} else if (util.hasRoles(req, [USER_ROLE.PROJECT_MANAGER])) {
targetRole = PROJECT_MEMBER_ROLE.PROJECT_MANAGER;
} else {
const err = new Error('Only copilot or manager is able to call this endpoint');
err.status = 401;
Expand Down
13 changes: 11 additions & 2 deletions src/routes/projectMembers/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,17 @@ const updateProjectMemberValdiations = {
body: {
param: Joi.object().keys({
isPrimary: Joi.boolean(),
role: Joi.any().valid(PROJECT_MEMBER_ROLE.CUSTOMER, PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER, PROJECT_MEMBER_ROLE.COPILOT, PROJECT_MEMBER_ROLE.OBSERVER).required(),
role: Joi.any().valid(
PROJECT_MEMBER_ROLE.CUSTOMER,
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.ACCOUNT_MANAGER,
PROJECT_MEMBER_ROLE.COPILOT,
PROJECT_MEMBER_ROLE.OBSERVER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.ACCOUNT_EXECUTIVE,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
).required(),
}),
},
};
Expand Down
4 changes: 3 additions & 1 deletion src/routes/projects/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,9 @@ module.exports = [
const members = req.context.currentProjectMembers;
const validRoles = [
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.MANAGER,
PROJECT_MEMBER_ROLE.PROGRAM_MANAGER,
PROJECT_MEMBER_ROLE.PROJECT_MANAGER,
PROJECT_MEMBER_ROLE.SOLUTION_ARCHITECT,
].map(x => x.toLowerCase());
const matchRole = role => _.indexOf(validRoles, role.toLowerCase()) >= 0;
if (updatedProps.status === PROJECT_STATUS.ACTIVE &&
Expand Down
3 changes: 1 addition & 2 deletions src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ import config from 'config';
import urlencode from 'urlencode';
import elasticsearch from 'elasticsearch';
import Promise from 'bluebird';
import models from './models';
// import AWS from 'aws-sdk';

import { ADMIN_ROLES, TOKEN_SCOPES, EVENT, PROJECT_MEMBER_ROLE, VALUE_TYPE, ESTIMATION_TYPE } from './constants';

const exec = require('child_process').exec;
const models = require('./models').default;
const tcCoreLibAuth = require('tc-core-library-js').auth;

const m2m = tcCoreLibAuth.m2m(config);
Expand Down Expand Up @@ -474,7 +474,6 @@ _.assignIn(util, {
req.log.debug('creating member', member);
let newMember = null;
// register member

return models.ProjectMember.create(member)
.then((_newMember) => {
newMember = _newMember.get({ plain: true });
Expand Down