Skip to content

PLAT-3453 Stop copilots from paying themselves #666

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 5 commits into from
Oct 5, 2023
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
4 changes: 2 additions & 2 deletions src/common/challenge-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
12 changes: 8 additions & 4 deletions src/common/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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) &&
Expand Down
62 changes: 56 additions & 6 deletions src/services/ChallengeService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down