Skip to content

Commit 3471a1c

Browse files
IsariesAaron-Detre
andauthored
fix(security): only remove a student from a run (#333)
The remove student endpoint took the target user straight from the student id and never checked it was a student. This fix checks that the target is a student before removing them. Co-authored-by: Aaron Detre <aarondetre@gmail.com>
1 parent 93e6ea1 commit 3471a1c

2 files changed

Lines changed: 92 additions & 0 deletions

File tree

src/main/java/org/wise/portal/presentation/web/controllers/teacher/management/RemoveStudentRunController.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ public void removeStudent(Authentication auth, @PathVariable Long runId,
3333
Run run = runService.retrieveById(runId);
3434
if (runService.hasWritePermission(auth, run)) {
3535
User studentUser = userService.retrieveById(studentId);
36+
if (!studentUser.isStudent()) {
37+
throw new AccessDeniedException(
38+
"User does not have permission to remove this user from run");
39+
}
3640
studentService.removeStudentFromRun(studentUser, run);
3741
} else {
3842
throw new AccessDeniedException("User does not have permission to remove student from run");
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.wise.portal.presentation.web.controllers.teacher.management;
2+
3+
import static org.easymock.EasyMock.expect;
4+
import static org.easymock.EasyMock.replay;
5+
import static org.easymock.EasyMock.verify;
6+
import static org.junit.Assert.fail;
7+
8+
import org.easymock.EasyMockExtension;
9+
import org.easymock.Mock;
10+
import org.easymock.TestSubject;
11+
import org.junit.jupiter.api.Test;
12+
import org.junit.jupiter.api.extension.ExtendWith;
13+
import org.springframework.security.access.AccessDeniedException;
14+
import org.wise.portal.presentation.web.controllers.APIControllerTest;
15+
import org.wise.portal.service.student.StudentService;
16+
17+
@ExtendWith(EasyMockExtension.class)
18+
public class RemoveStudentRunControllerTest extends APIControllerTest {
19+
20+
@TestSubject
21+
private RemoveStudentRunController controller = new RemoveStudentRunController();
22+
23+
@Mock
24+
private StudentService studentService;
25+
26+
private void replayServices() {
27+
replay(runService, studentService, userService);
28+
}
29+
30+
private void verifyServices() {
31+
verify(runService, studentService, userService);
32+
}
33+
34+
@Test
35+
public void removeStudent_NoWritePermission_ThrowAccessDenied() throws Exception {
36+
expect(runService.retrieveById(runId1)).andReturn(run1);
37+
expect(runService.hasWritePermission(teacherAuth, run1)).andReturn(false);
38+
replayServices();
39+
try {
40+
controller.removeStudent(teacherAuth, runId1, student1Id);
41+
fail("Expected AccessDeniedException to be thrown");
42+
} catch (AccessDeniedException e) {
43+
}
44+
verifyServices();
45+
}
46+
47+
@Test
48+
public void removeStudent_TargetUserIsTeacher_ThrowAccessDenied() throws Exception {
49+
expect(runService.retrieveById(runId1)).andReturn(run1);
50+
expect(runService.hasWritePermission(teacherAuth, run1)).andReturn(true);
51+
expect(userService.retrieveById(teacher2Id)).andReturn(teacher2);
52+
replayServices();
53+
try {
54+
controller.removeStudent(teacherAuth, runId1, teacher2Id);
55+
fail("Expected AccessDeniedException to be thrown");
56+
} catch (AccessDeniedException e) {
57+
}
58+
verifyServices();
59+
}
60+
61+
@Test
62+
public void removeStudent_TargetUserIsStudent_RemoveStudentFromRun() throws Exception {
63+
expect(runService.retrieveById(runId1)).andReturn(run1);
64+
expect(runService.hasWritePermission(teacherAuth, run1)).andReturn(true);
65+
expect(userService.retrieveById(student1Id)).andReturn(student1);
66+
studentService.removeStudentFromRun(student1, run1);
67+
replayServices();
68+
controller.removeStudent(teacherAuth, runId1, student1Id);
69+
verifyServices();
70+
}
71+
72+
/**
73+
* Both steps of removeStudentFromRun are scoped to the run, so a student who is not in the run
74+
* is left untouched. Rejecting them here instead would turn a repeated removal into an error,
75+
* and would leave a student who is still in a workgroup but no longer in a period with no way
76+
* to be cleaned up.
77+
*/
78+
@Test
79+
public void removeStudent_StudentNotInRun_RemoveStudentFromRun() throws Exception {
80+
expect(runService.retrieveById(runId1)).andReturn(run1);
81+
expect(runService.hasWritePermission(teacherAuth, run1)).andReturn(true);
82+
expect(userService.retrieveById(student2Id)).andReturn(student2);
83+
studentService.removeStudentFromRun(student2, run1);
84+
replayServices();
85+
controller.removeStudent(teacherAuth, runId1, student2Id);
86+
verifyServices();
87+
}
88+
}

0 commit comments

Comments
 (0)