Skip to content

Commit c0423bf

Browse files
committed
Remove unassigned tasks from response for challenge-list page in community-app (PM-5621)
1 parent 0d6ae0c commit c0423bf

2 files changed

Lines changed: 74 additions & 38 deletions

File tree

src/services/ChallengeService.js

Lines changed: 9 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1956,7 +1956,6 @@ async function searchChallenges(currentUser, criteria) {
19561956
const requestedMemberId = !_.isNil(criteria.memberId) ? _.toString(criteria.memberId) : null;
19571957
const currentUserMemberId =
19581958
currentUser && !_hasAdminRole && !_isMachineToken ? _.toString(currentUser.userId) : null;
1959-
const memberIdForTaskFilter = requestedMemberId || currentUserMemberId;
19601959
const isSelfMemberSearch = Boolean(
19611960
requestedMemberId && currentUserMemberId && requestedMemberId === currentUserMemberId,
19621961
);
@@ -2078,27 +2077,11 @@ async function searchChallenges(currentUser, criteria) {
20782077
});
20792078
}
20802079

2081-
// FIXME: Tech Debt
2082-
let excludeTasks = true;
2083-
if (requestedMemberId) {
2084-
// When we already restrict the result set to a specific member,
2085-
// rerunning the generic task visibility filter is redundant.
2086-
excludeTasks = false;
2087-
} else if (
2088-
currentUser &&
2089-
(_hasAdminRole || _isMachineToken || hasProjectManagerAccessForSearch)
2090-
) {
2091-
// if you're an admin or m2m, security rules wont be applied
2092-
excludeTasks = false;
2093-
}
2094-
20952080
/**
2096-
* For non-authenticated users:
2097-
* - Only unassigned tasks will be returned
2098-
* For authenticated users (non-admin):
2099-
* - Only unassigned tasks and tasks assigned to the logged in user will be returned
2100-
* For admins/m2m:
2101-
* - All tasks will be returned
2081+
* Task challenges are visible to ordinary authenticated users only when they
2082+
* have a resource on the task. Anonymous users never receive tasks. Admin,
2083+
* machine-token and project-manager searches retain their operational access.
2084+
* A requested memberId narrows results but never grants the caller task access.
21022085
*/
21032086
if (currentUser && (_hasAdminRole || _isMachineToken)) {
21042087
// For admins/m2m, allow filtering based on task properties
@@ -2117,28 +2100,16 @@ async function searchChallenges(currentUser, criteria) {
21172100
taskMemberId: criteria.taskMemberId,
21182101
});
21192102
}
2120-
} else if (excludeTasks) {
2121-
const taskFilter = [];
2122-
if (memberIdForTaskFilter) {
2103+
} else if (!hasProjectManagerAccessForSearch) {
2104+
const taskFilter = [{ taskIsTask: false }];
2105+
if (currentUserMemberId) {
21232106
taskFilter.push({
2107+
taskIsTask: true,
21242108
memberAccesses: {
2125-
some: { memberId: memberIdForTaskFilter },
2109+
some: { memberId: currentUserMemberId },
21262110
},
21272111
});
21282112
}
2129-
taskFilter.push({
2130-
taskIsTask: false,
2131-
});
2132-
taskFilter.push({
2133-
taskIsTask: true,
2134-
taskIsAssigned: false,
2135-
taskMemberId: null,
2136-
});
2137-
if (currentUser && !_hasAdminRole && !_isMachineToken) {
2138-
taskFilter.push({
2139-
taskMemberId: currentUser.userId,
2140-
});
2141-
}
21422113
prismaFilter.where.AND.push({
21432114
OR: taskFilter,
21442115
});

test/unit/ChallengeService.test.js

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,6 +1006,71 @@ describe("challenge service unit tests", () => {
10061006
should.equal(result.numOfRegistrants, 0);
10071007
});
10081008

1009+
it("search challenges hides unassigned tasks from anonymous users", async () => {
1010+
await prisma.challenge.update({
1011+
where: { id: data.taskChallenge.id },
1012+
data: {
1013+
taskIsAssigned: false,
1014+
taskMemberId: null,
1015+
},
1016+
});
1017+
1018+
try {
1019+
const result = await service.searchChallenges(undefined, {
1020+
id: data.taskChallenge.id,
1021+
});
1022+
1023+
should.equal(result.total, 0);
1024+
should.equal(result.result.length, 0);
1025+
} finally {
1026+
await prisma.challenge.update({
1027+
where: { id: data.taskChallenge.id },
1028+
data: { taskIsAssigned: true },
1029+
});
1030+
}
1031+
});
1032+
1033+
it("search challenges uses the caller resource association for task visibility", async () => {
1034+
const originalGetCompleteUserGroupTreeIds = helper.getCompleteUserGroupTreeIds;
1035+
const originalMemberChallengeAccessFindMany = prisma.memberChallengeAccess.findMany;
1036+
const originalChallengeFindMany = prisma.challenge.findMany;
1037+
let capturedWhere;
1038+
1039+
helper.getCompleteUserGroupTreeIds = async () => [];
1040+
prisma.memberChallengeAccess.findMany = async () => [{ challengeId: data.taskChallenge.id }];
1041+
prisma.challenge.findMany = async (query) => {
1042+
capturedWhere = query.where;
1043+
return [];
1044+
};
1045+
1046+
try {
1047+
const result = await service.searchChallenges(
1048+
{ roles: ["Topcoder User"], userId: "caller-resource-id" },
1049+
{ memberId: "different-member-id" },
1050+
);
1051+
1052+
should.equal(result.total, 0);
1053+
const taskVisibilityFilter = capturedWhere.AND.find(
1054+
(filter) => filter.OR && filter.OR.some((condition) => condition.taskIsTask === false),
1055+
);
1056+
taskVisibilityFilter.should.deep.equal({
1057+
OR: [
1058+
{ taskIsTask: false },
1059+
{
1060+
taskIsTask: true,
1061+
memberAccesses: {
1062+
some: { memberId: "caller-resource-id" },
1063+
},
1064+
},
1065+
],
1066+
});
1067+
} finally {
1068+
helper.getCompleteUserGroupTreeIds = originalGetCompleteUserGroupTreeIds;
1069+
prisma.memberChallengeAccess.findMany = originalMemberChallengeAccessFindMany;
1070+
prisma.challenge.findMany = originalChallengeFindMany;
1071+
}
1072+
});
1073+
10091074
it("search challenges sorts status alphabetically for member and non-member searches", async () => {
10101075
const statusChallenges = [
10111076
{

0 commit comments

Comments
 (0)