Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Improve challenge visibility control: review and search #508

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
Open
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
64 changes: 45 additions & 19 deletions actions/challenges.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@
* Changes in 1.31:
* - Remove screeningScorecardId and reviewScorecardId from search challenges api.
* Changes in 1.32:
* - validateChallenge, getRegistrants, getChallenge, getSubmissions and getPhases functions now check
* if an user belongs to a group via user_group_xref for old challenges and by calling V3 API for new ones.
* - validateChallenge, getRegistrants, getChallenge, getSubmissions, getPhases, searchChallenges and getChallenges
* functions now check if an user belongs to a group via user_group_xref for old challenges and by calling V3 API
* for new ones.
*/
"use strict";
/*jslint stupid: true, unparam: true, continue: true, nomen: true */
Expand Down Expand Up @@ -155,15 +156,22 @@ var CHALLENGE_TYPE_FILTER = ' AND p.project_category_id IN (@filter@)';
* This filter will return all private challenges.
* @since 1.24
*/
var ALL_PRIVATE_CHALLENGE_FILTER = ' EXISTS (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id)\n';
var ALL_PRIVATE_CHALLENGE_FILTER = '(@amIAdmin@ = 0 AND EXISTS (SELECT contest_id FROM contest_eligibility WHERE contest_id = p.project_id))\n';

/**
* This filter will return private challenges that given user_id can access.
* @since 1.24
*/
var USER_PRIVATE_CHALLENGE_FILTER = ' EXISTS (SELECT contest_id FROM contest_eligibility ce, ' +
'group_contest_eligibility gce, user_group_xref x WHERE x.login_id = @user_id@ AND x.group_id = gce.group_id ' +
'AND gce.contest_eligibility_id = ce.contest_eligibility_id AND ce.contest_id = p.project_id)\n';
var USER_PRIVATE_CHALLENGE_FILTER = ' EXISTS (SELECT contest_id ' +
'FROM contest_eligibility ce ' +
'INNER JOIN group_contest_eligibility gce ON gce.contest_eligibility_id = ce.contest_eligibility_id ' +
'INNER JOIN security_groups sg ON gce.group_id = sg.group_id ' +
'LEFT JOIN user_group_xref x ON x.group_id = gce.group_id ' +
'WHERE ce.contest_id = p.project_id AND (' +
'(sg.challenge_group_ind = 0 AND x.login_id = @userId@)' +
'OR (sg.challenge_group_ind <> 0 AND gce.group_id IN (@myGroups@))' +
')' +
')\n';

/**
* This filter return all private challenges that in specific group.
Expand Down Expand Up @@ -908,7 +916,8 @@ var searchChallenges = function (api, connection, dbConnectionMap, community, ne
total,
challengeType,
queryName,
challenges;
challenges,
myGroups;
for (prop in query) {
if (query.hasOwnProperty(prop)) {
query[prop.toLowerCase()] = query[prop];
Expand Down Expand Up @@ -963,7 +972,17 @@ var searchChallenges = function (api, connection, dbConnectionMap, community, ne
sqlParams.user_id = caller.userId || 0;

// Check the private challenge access
api.dataAccess.executeQuery('check_eligibility', sqlParams, dbConnectionMap, cb);
api.v3client.getMyGroups(connection, cb);
}, function (groups, cb) {
myGroups = groups;
sqlParams.amIAdmin = connection.caller.accessLevel === 'admin' ? 1 : 0;
sqlParams.myGroups = myGroups;
// Next function uses the passed parameter only to check if it's not empty
if(sqlParams.amIAdmin || !_.isDefined(query.communityId) || _.indexOf(myGroups, query.communityId) !== -1) {
cb(null, [1]);
} else {
api.dataAccess.executeQuery('check_eligibility', sqlParams, dbConnectionMap, cb);
}
}, function (results, cb) {
if (results.length === 0) {
// Return error if the user is not allowed to a specific group(communityId is set)
Expand Down Expand Up @@ -2310,7 +2329,8 @@ var getChallengeResults = function (api, connection, dbConnectionMap, isStudio,
cb(error);
return;
}

api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cb);
}, function (cb) {
sqlParams.challengeId = challengeId;
api.dataAccess.executeQuery("get_challenge_results_validations", sqlParams, dbConnectionMap, cb);
}, function (rows, cb) {
Expand All @@ -2319,11 +2339,6 @@ var getChallengeResults = function (api, connection, dbConnectionMap, isStudio,
return;
}

if (rows[0].is_private_challenge) {
cb(new NotFoundError('This is a private challenge. You cannot view it.'));
return;
}

if (!rows[0].is_challenge_finished) {
cb(new BadRequestError('You cannot view the results because the challenge is not yet finished or was cancelled.'));
return;
Expand Down Expand Up @@ -3677,7 +3692,8 @@ var getChallenges = function (api, connection, listType, isMyChallenges, next) {
result = {},
total,
challenges,
index;
index,
myGroups;
for (prop in query) {
if (query.hasOwnProperty(prop)) {
query[prop.toLowerCase()] = query[prop];
Expand Down Expand Up @@ -3723,11 +3739,15 @@ var getChallenges = function (api, connection, listType, isMyChallenges, next) {
},
function (cb) {
validateInputParameterV2(helper, caller, type, query, filter, pageIndex, pageSize, sortColumn, sortOrder, listType, dbConnectionMap, cb);

}, function (cb) {
api.v3client.getMyGroups(connection, cb);
}, function (groups, cb) {
myGroups = groups;

if (filter.technologies) {
filter.tech = filter.technologies.split(',')[0];
}
}, function (cb) {

if (pageIndex === -1) {
pageIndex = 1;
pageSize = helper.MAX_PAGE_SIZE;
Expand All @@ -3744,7 +3764,9 @@ var getChallenges = function (api, connection, listType, isMyChallenges, next) {
registration_phase_status: helper.LIST_TYPE_REGISTRATION_STATUS_MAP[listType],
project_status_id: helper.LIST_TYPE_PROJECT_STATUS_MAP[listType],
user_id: caller.userId || 0,
userId: caller.userId || 0
userId: caller.userId || 0,
amIAdmin: caller.accessLevel === 'admin' ? 1 : 0,
myGroups: myGroups
});

if (isMyChallenges) {
Expand All @@ -3757,7 +3779,11 @@ var getChallenges = function (api, connection, listType, isMyChallenges, next) {
}

// Check the private challenge access
api.dataAccess.executeQuery('check_eligibility', sqlParams, dbConnectionMap, cb);
if(sqlParams.amIAdmin || !_.isDefined(query.communityId) || _.indexOf(myGroups, query.communityId) !== -1) {
cb(null, [1]);
} else {
api.dataAccess.executeQuery('check_eligibility', sqlParams, dbConnectionMap, cb);
}
}, function (results, cb) {
if (results.length === 0) {
// Return error if the user is not allowed to a specific group(communityId is set)
Expand Down
49 changes: 28 additions & 21 deletions actions/reviewOpportunities.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
/*
* Copyright (C) 2013 - 2014 TopCoder Inc., All Rights Reserved.
* Copyright (C) 2013 - 2017 TopCoder Inc., All Rights Reserved.
*
* @version 1.6
* @author Sky_, Ghost_141, flytoj2ee
* @version 1.7
* @author Sky_, Ghost_141, flytoj2ee, GFalcon
* changes in 1.1
* - Implement the studio review opportunities.
* changes in 1.2
Expand All @@ -17,6 +17,10 @@
* - Implement the applyStudioReviewOpportunity api.
* Changes in 1.6:
* - Fix bug in review opportunities api.
* Changes in 1.7:
* - all functions check the right of the current user to view
* or apply to a challenge via user_group_xref for old challenges
* and by calling V3 API for new ones.
*/
'use strict';
/*jslint node: true, stupid: true, unparam: true, plusplus: true */
Expand Down Expand Up @@ -212,6 +216,7 @@ var getStudioReviewOpportunity = function (api, connection, dbConnectionMap, nex
cb(error);
return;
}

sqlParams.challengeId = challengeId;
api.dataAccess.executeQuery('get_studio_review_opportunity_phases', sqlParams, dbConnectionMap, cb);
}, function (rows, cb) {
Expand All @@ -229,6 +234,9 @@ var getStudioReviewOpportunity = function (api, connection, dbConnectionMap, nex
duration: getDuration(row.start_time, row.end_time) + ' hours'
});
});
// Check if the user has the right to view the challenge
api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cb);
}, function (cb) {
api.dataAccess.executeQuery('get_studio_review_opportunity_positions', sqlParams, dbConnectionMap, cb);
}, function (rows, cb) {
if (rows.length !== 0) {
Expand Down Expand Up @@ -467,6 +475,7 @@ var calculatePayment = function (reviewOpportunityInfo, adjustPayments) {
*/
var getPaymentValues = function (reviewOpportunityInfo, adjustPayments, isAsc) {
var payment = calculatePayment(reviewOpportunityInfo, adjustPayments);

return _.sortBy(payment, function (item) { return (isAsc ? -item : item); });
};

Expand All @@ -479,7 +488,7 @@ var getPaymentValues = function (reviewOpportunityInfo, adjustPayments, isAsc) {
*/
var getReviewOpportunities = function (api, connection, isStudio, next) {
var helper = api.helper, result = {}, sqlParams, dbConnectionMap = connection.dbConnectionMap, pageIndex,
pageSize, sortOrder, sortColumn, filter = {}, reviewOpportunities, queryName;
pageSize, sortOrder, sortColumn, filter = {}, reviewOpportunities, queryName, myGroups;

pageSize = Number(connection.params.pageSize || helper.MAX_PAGE_SIZE);
pageIndex = Number(connection.params.pageIndex || 1);
Expand All @@ -491,6 +500,11 @@ var getReviewOpportunities = function (api, connection, isStudio, next) {
validateInputParameter(helper, connection, filter, isStudio, cb);
},
function (cb) {
api.v3client.getMyGroups(connection, cb)
},
function (groups, cb) {

myGroups = groups;

if (pageIndex === -1) {
pageIndex = 1;
Expand All @@ -512,7 +526,10 @@ var getReviewOpportunities = function (api, connection, isStudio, next) {
reviewStartDateSecondDate: filter.reviewStartDate.secondDate,
reviewEndDateFirstDate: filter.reviewEndDate.firstDate,
reviewEndDateSecondDate: filter.reviewEndDate.secondDate,
challenge_id: 0
challenge_id: 0,
amIAdmin: connection.caller.accessLevel === 'admin' ? 1 : 0,
myId: (connection.caller.userId || 0),
myGroups: myGroups
};

queryName = isStudio ? 'search_studio_review_opportunities' : 'search_software_review_opportunities_data';
Expand Down Expand Up @@ -653,7 +670,9 @@ var getSoftwareReviewOpportunity = function (api, connection, next) {
};

async.parallel({
privateCheck: execQuery('check_user_challenge_accessibility'),
privateCheck: function (cbx) {
api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cbx);
},
reviewCheck: execQuery('check_challenge_review_opportunity')
}, cb);
},
Expand All @@ -664,11 +683,6 @@ var getSoftwareReviewOpportunity = function (api, connection, next) {
return;
}

if (result.privateCheck[0].is_private && !result.privateCheck[0].has_access) {
cb(new ForbiddenError('The user is not allowed to visit this challenge review opportunity detail.'));
return;
}

async.parallel({
basic: execQuery('get_review_opportunity_detail_basic'),
phases: execQuery('get_review_opportunity_detail_phases'),
Expand Down Expand Up @@ -864,8 +878,8 @@ var applyDevelopReviewOpportunity = function (api, connection, next) {
api.dataAccess.executeQuery('check_reviewer', { challenge_id: challengeId, user_id: caller.userId }, dbConnectionMap, cbx);
},
privateCheck: function (cbx) {
api.dataAccess.executeQuery('check_user_challenge_accessibility', { challengeId: challengeId, user_id: caller.userId }, dbConnectionMap, cbx);
}
api.challengeHelper.checkUserChallengeEligibility(connection, challengeId, cbx);
},
}, cb);
},
function (res, cb) {
Expand All @@ -875,7 +889,6 @@ var applyDevelopReviewOpportunity = function (api, connection, next) {
.map(function (item) { return item.review_application_role_id; })
.uniq()
.value(),
privateCheck = res.privateCheck[0],
positionsLeft,
assignedResourceRoles = res.resourceRoles,
currentUserResourceRole = _.filter(assignedResourceRoles, function (item) { return item.user_id === caller.userId; });
Expand Down Expand Up @@ -908,13 +921,7 @@ var applyDevelopReviewOpportunity = function (api, connection, next) {
return;
}

// The caller can't access this private challenge.
if (privateCheck.is_private && !privateCheck.has_access) {
cb(new ForbiddenError('The user is not allowed to register this challenge review.'));
return;
}

// We only check this when caller is trying to apply review opportunity not cancel them.
// We only check this when caller is trying to apply review opportunity not cancel them.
// Get the review resource role that belong to the caller. If the length > 0 then the user is a reviewer already.
if (reviewApplicationRoleId.length > 0 && currentUserResourceRole.length > 0) {
cb(new BadRequestError('You are already assigned as reviewer for the contest.'));
Expand Down
25 changes: 25 additions & 0 deletions db_scripts/test_eligibility.delete.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,43 @@ DATABASE informixoltp;

-- UPDATE coder SET comp_country_code = NULL WHERE user_id = 132458;

DATABASE tcs_dw;

DELETE FROM project WHERE project_id > 4440000 AND project_id < 4440100;

DATABASE tcs_catalog;

DELETE FROM project_info WHERE project_id > 5550000 AND project_id < 5550100;
DELETE FROM project_phase WHERE project_id > 5550000 AND project_id < 5550100;
DELETE FROM project WHERE project_id > 5550000 AND project_id < 5550100;

DELETE FROM project_info WHERE project_id > 4440000 AND project_id < 4440100;
DELETE FROM project_phase WHERE project_id > 4440000 AND project_id < 4440100;
DELETE FROM prize WHERE project_id > 4440000 AND project_id < 4440100;
DELETE FROM project WHERE project_id > 4440000 AND project_id < 4440100;

DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE project_id > 3330000 AND project_id < 3330100);
DELETE FROM resource WHERE project_id > 3330000 AND project_id < 3330100;
DELETE FROM project_info WHERE project_id > 3330000 AND project_id < 3330100;
DELETE FROM project_phase WHERE project_id > 3330000 AND project_id < 3330100;
DELETE FROM project WHERE project_id > 3330000 AND project_id < 3330100;
DELETE FROM project_studio_specification WHERE project_studio_spec_id = 3330333;

DELETE FROM prize WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM project_payment_adjustment WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM notification WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM project_result WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM project_user_audit WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM component_inquiry WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM resource_info WHERE resource_id IN (SELECT resource_id FROM resource WHERE project_id > 1110000 AND project_id < 1110100);
DELETE FROM resource WHERE project_id > 1110000 AND project_id < 1110100;

DELETE FROM review_application WHERE review_auction_id IN (SELECT review_auction_id FROM review_auction WHERE project_id > 1110000 AND project_id < 1110100);
DELETE FROM review_auction WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM project_info WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM comp_versions WHERE component_id = 3330333;
DELETE FROM comp_catalog WHERE component_id = 3330333;
DELETE FROM phase_criteria WHERE project_phase_id > 1110000 AND project_phase_id < 1110100;
DELETE FROM project_phase WHERE project_id > 1110000 AND project_id < 1110100;
DELETE FROM project WHERE project_id > 1110000 AND project_id < 1110100;

Expand Down
Loading