From 55ceac164b3512a53ea465aa3bc2658799c43c70 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 13 Apr 2023 16:57:07 +0300 Subject: [PATCH 1/6] temp fix to allow modifications of the timelineTemplateId --- src/services/ChallengeService.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index f96c4a9e..42444ae2 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1617,16 +1617,17 @@ async function updateChallenge(currentUser, challengeId, data) { const finalStatus = data.status || challenge.status; const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId; const timelineTemplateChanged = false; - if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { - if ( - finalStatus !== constants.challengeStatuses.New && - finalTimelineTemplateId !== challenge.timelineTemplateId - ) { - throw new errors.BadRequestError( - `Cannot change the timelineTemplateId for challenges with status: ${finalStatus}` - ); - } - } else if (finalTimelineTemplateId !== challenge.timelineTemplateId) { + // if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { + // if ( + // finalStatus !== constants.challengeStatuses.New && + // finalTimelineTemplateId !== challenge.timelineTemplateId + // ) { + // throw new errors.BadRequestError( + // `Cannot change the timelineTemplateId for challenges with status: ${finalStatus}` + // ); + // } + // } else + if (finalTimelineTemplateId !== challenge.timelineTemplateId) { // make sure there are no previous phases if the timeline template has changed challenge.phases = []; timelineTemplateChanged = true; From 21eabaa9477e0c36cab4b91d1d935b880e19fefe Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 13 Apr 2023 17:05:29 +0300 Subject: [PATCH 2/6] fix assignment to constant variable --- src/services/ChallengeService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 42444ae2..7ac329b5 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1616,7 +1616,7 @@ async function updateChallenge(currentUser, challengeId, data) { // TODO: Fix this Tech Debt once legacy is turned off const finalStatus = data.status || challenge.status; const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId; - const timelineTemplateChanged = false; + let timelineTemplateChanged = false; // if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { // if ( // finalStatus !== constants.challengeStatuses.New && From 5ca159b4aa4540ee6d2e8fb2f87a0a6f0ba47b10 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 13 Apr 2023 17:16:10 +0300 Subject: [PATCH 3/6] remove temp fix --- src/services/ChallengeService.js | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 7ac329b5..f1e159e8 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1617,17 +1617,16 @@ async function updateChallenge(currentUser, challengeId, data) { const finalStatus = data.status || challenge.status; const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId; let timelineTemplateChanged = false; - // if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { - // if ( - // finalStatus !== constants.challengeStatuses.New && - // finalTimelineTemplateId !== challenge.timelineTemplateId - // ) { - // throw new errors.BadRequestError( - // `Cannot change the timelineTemplateId for challenges with status: ${finalStatus}` - // ); - // } - // } else - if (finalTimelineTemplateId !== challenge.timelineTemplateId) { + if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { + if ( + finalStatus !== constants.challengeStatuses.New && + finalTimelineTemplateId !== challenge.timelineTemplateId + ) { + throw new errors.BadRequestError( + `Cannot change the timelineTemplateId for challenges with status: ${finalStatus}` + ); + } + } else if (finalTimelineTemplateId !== challenge.timelineTemplateId) { // make sure there are no previous phases if the timeline template has changed challenge.phases = []; timelineTemplateChanged = true; From 39d249ab129d8f6a05c6c2afaa8613e2b72d1886 Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 13 Apr 2023 17:45:11 +0300 Subject: [PATCH 4/6] allow admins/m2m to change the timelineTemplateId --- src/services/ChallengeService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index f96c4a9e..6bc788c6 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1616,8 +1616,8 @@ async function updateChallenge(currentUser, challengeId, data) { // TODO: Fix this Tech Debt once legacy is turned off const finalStatus = data.status || challenge.status; const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId; - const timelineTemplateChanged = false; - if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { + let timelineTemplateChanged = false; + if (!currentUser.isMachine && !hasAdminRole(currentUser) && !_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { if ( finalStatus !== constants.challengeStatuses.New && finalTimelineTemplateId !== challenge.timelineTemplateId @@ -1750,7 +1750,7 @@ async function updateChallenge(currentUser, challengeId, data) { const { track, type } = await challengeHelper.validateAndGetChallengeTypeAndTrack({ typeId: challenge.typeId, trackId: challenge.trackId, - timelineTemplateId: challenge.timelineTemplateId, + timelineTemplateId: timelineTemplateChanged ? finalTimelineTemplateId : challenge.timelineTemplateId, }); if (_.get(type, "isTask")) { From c696428e799c7a52c5dbb7e663b6a4d4b1ba607f Mon Sep 17 00:00:00 2001 From: Thomas Kranitsas Date: Thu, 13 Apr 2023 17:53:18 +0300 Subject: [PATCH 5/6] fix syntax error --- src/services/ChallengeService.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 3a29c2ff..6bc788c6 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1618,7 +1618,6 @@ async function updateChallenge(currentUser, challengeId, data) { const finalTimelineTemplateId = data.timelineTemplateId || challenge.timelineTemplateId; let timelineTemplateChanged = false; if (!currentUser.isMachine && !hasAdminRole(currentUser) && !_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { - if (!_.get(data, "legacy.pureV5") && !_.get(challenge, "legacy.pureV5")) { if ( finalStatus !== constants.challengeStatuses.New && finalTimelineTemplateId !== challenge.timelineTemplateId From 665e532b0dd09072f6bd3753ed92c8896b52b5fe Mon Sep 17 00:00:00 2001 From: liuliquan Date: Thu, 5 Oct 2023 08:09:18 -0500 Subject: [PATCH 6/6] PLAT-3453 Stop copilots from paying themselves (#666) * PLAT-3453 Stop copilots from paying themselves 1. Stop copilots from paying themselves, thus copilots will need to contact manager to launch/complete the task 2. When updateChallenge, the helper.getChallengeResources function is called multiple times, now it's called only once * Only restrict launch/complete self-assigned Task Also validate winners is present in request payload when complete a Task * Task status change (#665) --------- Co-authored-by: Rakib Ansary --- src/common/challenge-helper.js | 4 +-- src/common/helper.js | 12 ++++--- src/services/ChallengeService.js | 62 ++++++++++++++++++++++++++++---- 3 files changed, 66 insertions(+), 12 deletions(-) diff --git a/src/common/challenge-helper.js b/src/common/challenge-helper.js index aef2bd1a..ac691cdb 100644 --- a/src/common/challenge-helper.js +++ b/src/common/challenge-helper.js @@ -105,9 +105,9 @@ class ChallengeHelper { } } - async validateChallengeUpdateRequest(currentUser, challenge, data) { + async validateChallengeUpdateRequest(currentUser, challenge, data, challengeResources) { if (process.env.LOCAL != "true") { - await helper.ensureUserCanModifyChallenge(currentUser, challenge); + await helper.ensureUserCanModifyChallenge(currentUser, challenge, challengeResources); } helper.ensureNoDuplicateOrNullElements(data.tags, "tags"); diff --git a/src/common/helper.js b/src/common/helper.js index f39800f0..46db3d85 100644 --- a/src/common/helper.js +++ b/src/common/helper.js @@ -528,14 +528,17 @@ async function getResourceRoles() { * Check if a user has full access on a challenge * @param {String} challengeId the challenge UUID * @param {String} userId the user ID + * @param {Array} challengeResources the challenge resources */ -async function userHasFullAccess(challengeId, userId) { +async function userHasFullAccess(challengeId, userId, challengeResources) { const resourceRoles = await getResourceRoles(); const rolesWithFullAccess = _.map( _.filter(resourceRoles, (r) => r.fullWriteAccess), "id" ); - const challengeResources = await getChallengeResources(challengeId); + if (!challengeResources) { + challengeResources = await getChallengeResources(challengeId); + } return ( _.filter( challengeResources, @@ -982,13 +985,14 @@ async function ensureUserCanViewChallenge(currentUser, challenge) { * * @param {Object} currentUser the user who perform operation * @param {Object} challenge the challenge to check + * @param {Array} challengeResources the challenge resources * @returns {Promise} */ -async function ensureUserCanModifyChallenge(currentUser, challenge) { +async function ensureUserCanModifyChallenge(currentUser, challenge, challengeResources) { // check groups authorization await ensureAccessibleByGroupsAccess(currentUser, challenge); // check full access - const isUserHasFullAccess = await userHasFullAccess(challenge.id, currentUser.userId); + const isUserHasFullAccess = await userHasFullAccess(challenge.id, currentUser.userId, challengeResources); if ( !currentUser.isMachine && !hasAdminRole(currentUser) && diff --git a/src/services/ChallengeService.js b/src/services/ChallengeService.js index 9b0fd8da..4b1e9e2d 100644 --- a/src/services/ChallengeService.js +++ b/src/services/ChallengeService.js @@ -1364,10 +1364,9 @@ function isDifferentPrizeSets(prizeSets = [], otherPrizeSets = []) { /** * Validate the winners array. * @param {Array} winners the Winner Array - * @param {String} winchallengeIdners the challenge ID + * @param {Array} challengeResources the challenge resources */ -async function validateWinners(winners, challengeId) { - const challengeResources = await helper.getChallengeResources(challengeId); +async function validateWinners(winners, challengeResources) { const registrants = _.filter(challengeResources, (r) => r.roleId === config.SUBMITTER_ROLE_ID); for (const prizeType of _.values(constants.prizeSetTypes)) { const filteredWinners = _.filter(winners, (w) => w.type === prizeType); @@ -1410,6 +1409,55 @@ async function validateWinners(winners, challengeId) { } } +/** + * Task shouldn't be launched/completed when it is assigned to the current user self. + * E.g: stop copilots from paying themselves, thus copilots will need to contact manager to launch/complete the task. + * @param {Object} currentUser the user who perform operation + * @param {Object} challenge the existing challenge + * @param {Object} data the new input challenge data + * @param {Array} challengeResources the challenge resources + */ +function validateTask(currentUser, challenge, data, challengeResources) { + if (!_.get(challenge, "legacy.pureV5Task")) { + // Not a Task + return; + } + + // Status changed to Active, indicating launch a Task + const isLaunchTask = + data.status === constants.challengeStatuses.Active && + challenge.status !== constants.challengeStatuses.Active; + + // Status changed to Completed, indicating complete a Task + const isCompleteTask = + data.status === constants.challengeStatuses.Completed && + challenge.status !== constants.challengeStatuses.Completed; + + // When complete a Task, input data should have winners + if (isCompleteTask && (!data.winners || !data.winners.length)) { + throw new errors.BadRequestError("The winners is required to complete a Task"); + } + + if (!currentUser.isMachine && (isLaunchTask || isCompleteTask)) { + // Whether task is assigned to current user + const assignedToCurrentUser = + _.filter( + challengeResources, + (r) => + r.roleId === config.SUBMITTER_ROLE_ID && + _.toString(r.memberId) === _.toString(currentUser.userId) + ).length > 0; + + if (assignedToCurrentUser) { + throw new errors.ForbiddenError( + `You are not allowed to ${ + data.status === constants.challengeStatuses.Active ? "lanuch" : "complete" + } task assigned to yourself. Please contact manager to operate.` + ); + } + } +} + /** * Update challenge. * @param {Object} currentUser the user who perform operation @@ -1441,7 +1489,10 @@ async function updateChallenge(currentUser, challengeId, data) { data = sanitizeData(sanitizeChallenge(data), challenge); logger.debug(`Sanitized Data: ${JSON.stringify(data)}`); - await validateChallengeUpdateRequest(currentUser, challenge, data); + const challengeResources = await helper.getChallengeResources(challengeId); + + await validateChallengeUpdateRequest(currentUser, challenge, data, challengeResources); + validateTask(currentUser, challenge, data, challengeResources); let sendActivationEmail = false; let sendSubmittedEmail = false; @@ -1716,7 +1767,7 @@ async function updateChallenge(currentUser, challengeId, data) { } if (data.winners && data.winners.length && data.winners.length > 0) { - await validateWinners(data.winners, challengeId); + await validateWinners(data.winners, challengeResources); } // Only m2m tokens are allowed to modify the `task.*` information on a challenge @@ -1777,7 +1828,6 @@ async function updateChallenge(currentUser, challengeId, data) { if (_.get(type, "isTask")) { if (!_.isEmpty(_.get(data, "task.memberId"))) { - const challengeResources = await helper.getChallengeResources(challengeId); const registrants = _.filter( challengeResources, (r) => r.roleId === config.SUBMITTER_ROLE_ID