diff --git a/build.gradle b/build.gradle
index 2db4c2a9130d2e13c72f98dd3b2b56ef8108487f..bd129852b1516184b9d622f9b55dae687f6b70c0 100644
--- a/build.gradle
+++ b/build.gradle
@@ -27,7 +27,7 @@ allprojects {
apply plugin: ScompPlugin
group 'nl.tudelft.ewi'
- version = '2.0.0'
+ version = '2.1.0'
sourceCompatibility = '1.11'
targetCompatibility = '1.11'
diff --git a/core/build.gradle b/core/build.gradle
index daaa103016ce6cbea9202804543771862bf21f81..90ab169144773dcfdb5c7701265c0f125893bb18 100644
--- a/core/build.gradle
+++ b/core/build.gradle
@@ -44,10 +44,13 @@ dependencies {
testImplementation 'org.mockito:mockito-core:2.+'
testImplementation 'org.springframework:spring-test:5.1.+'
+ testImplementation 'org.springframework.boot:spring-boot-starter-test:2.1.+'
+ testImplementation 'org.springframework.security:spring-security-test:5.1.+'
testImplementation 'org.hamcrest:hamcrest-all:1.3'
testImplementation 'com.jayway.jsonpath:json-path:2.4.+'
testImplementation 'nl.jqno.equalsverifier:equalsverifier:3.1.+'
testImplementation 'org.skyscreamer:jsonassert:0.9.0'
+ testImplementation 'de.flapdoodle.embed:de.flapdoodle.embed.mongo:2.2.+'
testImplementation project(':test-common')
}
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/Core.java b/core/src/main/java/nl/tudelft/ewi/auta/core/Core.java
index 69fb1ac22d45930313b3f565b3c6e9d48ed0d677..993c6f9df7950c15af36a93fece2b2ce32afaa4d 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/Core.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/Core.java
@@ -118,7 +118,8 @@ public class Core implements WebMvcConfigurer {
System.setProperty("org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH", "true");
- final var spring = new SpringApplicationBuilder(Core.class).logStartupInfo(false).run(args);
+ final var spring =
+ new SpringApplicationBuilder(Core.class).logStartupInfo(false).run(args);
logger.info("Starting AuTA Core...");
final var env = spring.getBean(Environment.class);
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/benchmarking/Benchmarker.java b/core/src/main/java/nl/tudelft/ewi/auta/core/benchmarking/Benchmarker.java
index 5a9d83e62175342a0260dda3572ebc6fd9dc7075..b5cf69670a64a355d61c61986a30e8a943f2ef55 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/benchmarking/Benchmarker.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/benchmarking/Benchmarker.java
@@ -180,7 +180,7 @@ public class Benchmarker {
}
final Map<Integer, List<Entity>> aggregatedMetrics = new HashMap<>();
//Filters children by level
- final var allFilteredChildren = projectEntity.getAllChildren().stream()
+ final var allFilteredChildren = projectEntity.getAllEntities().stream()
.filter(e -> e.getLevel().equals(level))
.collect(Collectors.toSet());
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/AssignmentController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/AssignmentController.java
index 1d274fa3cf22be9200078bfd5fce9978f2697e94..fa36fa8df50f5a669912bf4bf9f25064b3780ab6 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/AssignmentController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/AssignmentController.java
@@ -1,18 +1,10 @@
package nl.tudelft.ewi.auta.core.controller;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-import javax.servlet.http.HttpServletRequest;
-
import nl.tudelft.ewi.auta.common.model.metric.MetricName;
import nl.tudelft.ewi.auta.common.model.metric.MetricSettings;
+import nl.tudelft.ewi.auta.core.database.AssignmentRepository;
+import nl.tudelft.ewi.auta.core.model.Assignment;
+import nl.tudelft.ewi.auta.core.response.Response;
import nl.tudelft.ewi.auta.core.response.exception.AssignmentAlreadyExistsException;
import nl.tudelft.ewi.auta.core.response.exception.InvalidAssignmentNameException;
import nl.tudelft.ewi.auta.core.response.exception.InvalidLanguageException;
@@ -21,6 +13,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@@ -29,9 +22,15 @@ import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
-import nl.tudelft.ewi.auta.core.database.AssignmentRepository;
-import nl.tudelft.ewi.auta.core.model.Assignment;
-import nl.tudelft.ewi.auta.core.response.Response;
+import javax.servlet.http.HttpServletRequest;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
/**
* The controller handling requests related to assignmentStore.
@@ -47,13 +46,21 @@ public class AssignmentController extends ControllerBase {
*/
private final AssignmentRepository assignmentStore;
+
+ /**
+ * The service that manages course-level security for this controller.
+ */
+ private final CourseSecuredService securedService;
+
/**
* Creates a new assignment controller.
*
* @param assignmentStore the assignment repository
*/
- public AssignmentController(final AssignmentRepository assignmentStore) {
+ public AssignmentController(final AssignmentRepository assignmentStore,
+ final CourseSecuredService securedService) {
this.assignmentStore = assignmentStore;
+ this.securedService = securedService;
}
/**
@@ -63,27 +70,31 @@ public class AssignmentController extends ControllerBase {
@GetMapping(value = "/api/v1/assignment", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> getAllAction(final HttpServletRequest req) {
final var res = new Response();
-
- final List<?> assignmentsList;
+ final List<Assignment> assignmentsList;
if (req.getParameterMap().containsKey("include_deleted")) {
assignmentsList = this.assignmentStore.findAll();
} else {
assignmentsList = this.assignmentStore.findAllActive();
}
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
+ final var assignmentsWithAccess = assignmentsList.stream()
+ .filter(assignment ->
+ this.securedService.userHasAssignmentAccess(authentication, assignment))
+ .collect(Collectors.toList());
- res.put("assignments", assignmentsList);
+ res.put("assignments", assignmentsWithAccess);
return ResponseEntity.ok(res);
}
- /**
- * Creates a new assignment and adds it to the store.
- * By default, no checks will be run if no static or dynamic checks are specified.
- *
- * @param req the request body
- *
- * @return the response
- */
+ /**
+ * Creates a new assignment and adds it to the store.
+ * By default, no checks will be run if no static or dynamic checks are specified.
+ *
+ * @param req the request body
+ *
+ * @return the response
+ */
@PostMapping(value = "/api/v1/assignment", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> createAction(final @RequestBody Map<String, Object> req)
throws URISyntaxException {
@@ -122,8 +133,8 @@ public class AssignmentController extends ControllerBase {
@GetMapping(value = "/api/v1/assignment/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> getAction(final @PathVariable String id) {
final var res = new Response();
-
final var assignment = this.assignmentStore.findExisting(id);
+ this.securedService.checkForAssignmentAccess(assignment);
final var options = new HashMap<String, Object>();
options.put("static", assignment.getMetricSettings());
@@ -137,6 +148,12 @@ public class AssignmentController extends ControllerBase {
return ResponseEntity.ok(res);
}
+ /**
+ * Updates an assignment.
+ *
+ * @param id the id of the assignment to update
+ * @return the response
+ */
@PutMapping(value = "/api/v1/assignment/{id}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> updateAction(
final @PathVariable String id,
@@ -145,6 +162,7 @@ public class AssignmentController extends ControllerBase {
final var res = new Response();
var assignment = this.assignmentStore.findExisting(id);
+ this.securedService.checkForAssignmentAccess(assignment);
this.populateFromRequest(req, assignment);
@@ -167,6 +185,7 @@ public class AssignmentController extends ControllerBase {
final var res = new Response();
var assignment = this.assignmentStore.findExisting(id);
+ this.securedService.checkForAssignmentAccess(assignment);
assignment.setDeleted(true);
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingController.java
index c6bad949b3a7c98c57f87e2be62806bffea0de35..13338331f7421443b3a312cb5c0f5288f8cd544f 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingController.java
@@ -67,6 +67,11 @@ public class BenchmarkingController extends ControllerBase {
*/
private final BenchmarkingReportGenerator generator;
+ /**
+ * The service that manages course-level security for this controller.
+ */
+ private final CourseSecuredService securedService;
+
/**
* Creates a benchmarking controller.
* @param entityRepository the repository where the entities are stored
@@ -76,11 +81,13 @@ public class BenchmarkingController extends ControllerBase {
public BenchmarkingController(final EntityRepository entityRepository,
final BadgeGenerator badgeGenerator,
final Benchmarker benchmarker,
- final BenchmarkingReportGenerator generator) {
+ final BenchmarkingReportGenerator generator,
+ final CourseSecuredService securedService) {
this.entityRepository = entityRepository;
this.badgeGenerator = badgeGenerator;
this.benchmarker = benchmarker;
this.generator = generator;
+ this.securedService = securedService;
}
/**
@@ -98,6 +105,7 @@ public class BenchmarkingController extends ControllerBase {
final @RequestBody BenchmarkingData data) throws IOException {
final var res = new Response();
final var entityContainer = this.entityRepository.findExisting(aid, sid);
+ this.securedService.checkForEntityContainerAccess(entityContainer);
final var projectEntity = entityContainer.getEntity();
projectEntity.postProcess();
@@ -130,7 +138,9 @@ public class BenchmarkingController extends ControllerBase {
final @PathVariable String aid,
final @PathVariable String sid) throws IOException, TemplateException {
final var res = new Response();
- final var projectEntity = this.entityRepository.findExisting(aid, sid).getEntity();
+ final var entityContainer = this.entityRepository.findExisting(aid, sid);
+ this.securedService.checkForEntityContainerAccess(entityContainer);
+ final var projectEntity = entityContainer.getEntity();
final var badge = this.badgeGenerator.generateBadge(projectEntity, aid);
res.put("badge", badge);
return ResponseEntity.ok(res);
@@ -192,7 +202,9 @@ public class BenchmarkingController extends ControllerBase {
final @PathVariable String sid,
final @RequestBody BenchmarkingData data) {
final var res = new Response();
- final var projectEntity = this.entityRepository.findExisting(aid, sid).getEntity();
+ final var entityContainer = this.entityRepository.findExisting(aid, sid);
+ this.securedService.checkForEntityContainerAccess(entityContainer);
+ final var projectEntity = entityContainer.getEntity();
data.validateMetricAndEntity();
projectEntity.postProcess();
final var chartData = this.generator.getChartData(projectEntity, data.getMetricName(),
@@ -216,7 +228,9 @@ public class BenchmarkingController extends ControllerBase {
final @RequestParam(defaultValue = "entity") String type,
final @RequestBody BenchmarkingData data) throws IOException {
final var res = new Response();
- final var projectEntity = this.entityRepository.findExisting(aid, sid).getEntity();
+ final var entityContainer = this.entityRepository.findExisting(aid, sid);
+ this.securedService.checkForEntityContainerAccess(entityContainer);
+ final var projectEntity = entityContainer.getEntity();
projectEntity.postProcess();
data.validateMetricAndEntity();
final @Nonnull var metricName = data.getMetricName();
@@ -259,7 +273,9 @@ public class BenchmarkingController extends ControllerBase {
final @PathVariable String sid,
final @RequestBody BenchmarkingData data) throws IOException, TemplateException {
- final var projectEntity = this.entityRepository.findExisting(aid, sid).getEntity();
+ final var entityContainer = this.entityRepository.findExisting(aid, sid);
+ this.securedService.checkForEntityContainerAccess(entityContainer);
+ final var projectEntity = entityContainer.getEntity();
data.validateMetricAndEntity();
projectEntity.postProcess();
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseController.java
index 2aa5207fca0f68ec27e732185e05dbc53c5e4122..655ae8b08097bfba48127d8fe7b144916fbd50f5 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseController.java
@@ -15,6 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
@@ -30,6 +31,7 @@ import org.springframework.web.bind.annotation.RequestBody;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
/**
@@ -60,16 +62,6 @@ public class CourseController extends ControllerBase {
*/
private final CourseSecuredService securedService;
- /**
- * Initializes the binders used in this controller, which is a validator for the createNewCourse
- * endpoint.
- * @param webDataBinder the data binder.
- */
- @InitBinder("course")
- public void initBinder(final WebDataBinder webDataBinder) {
- webDataBinder.addValidators(new CourseValidator());
- }
-
/**
* Creates a course controller.
* @param repositories the repositories
@@ -82,14 +74,31 @@ public class CourseController extends ControllerBase {
this.securedService = securedService;
}
+ /**
+ * Initializes the binders used in this controller, which is a validator for the createNewCourse
+ * endpoint.
+ *
+ * @param webDataBinder the data binder.
+ */
+ @InitBinder("course")
+ public void initBinder(final WebDataBinder webDataBinder) {
+ webDataBinder.addValidators(new CourseValidator());
+ }
+
/**
* Gets all courses.
+ *
* @return the list of all courses
*/
@GetMapping(value = "/api/v1/course", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> getAllAction() {
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
final var res = new Response();
- res.put("courses", this.repositories.getCourseRepository().findAll());
+ final var courses = this.repositories.getCourseRepository().findAll();
+ final var coursesWithAccess = courses.stream()
+ .filter(course -> this.securedService.userHasCourseAccess(authentication, course))
+ .collect(Collectors.toList());
+ res.put("courses", coursesWithAccess);
return ResponseEntity.ok(res);
}
@@ -123,7 +132,8 @@ public class CourseController extends ControllerBase {
@DeleteMapping(value = "/api/v1/course/{cid}", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> deleteCourseAction(final @PathVariable String cid) {
final var res = new Response();
- this.repositories.getCourseRepository().findExisting(cid);
+ final var course = this.repositories.getCourseRepository().findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
this.repositories.getCourseRepository().deleteById(cid);
return ResponseEntity.ok(res);
}
@@ -143,6 +153,7 @@ public class CourseController extends ControllerBase {
final var courseRepository = this.repositories.getCourseRepository();
final var oldCourse = courseRepository.findExisting(cid);
+ this.securedService.checkForCourseAccess(oldCourse);
oldCourse.setName(course.getName());
oldCourse.setCourseCode(course.getCourseCode());
@@ -159,14 +170,14 @@ public class CourseController extends ControllerBase {
public ResponseEntity<Response> getCourseAction(
final @PathVariable String cid) {
final var res = new Response();
-
final var courseRepository = this.repositories.getCourseRepository();
- res.put("course", courseRepository.findExisting(cid));
+ final var course = courseRepository.findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
+ res.put("course", course);
return ResponseEntity.ok(res);
}
-
/**
* Adds an assignment to a course.
*
@@ -195,6 +206,7 @@ public class CourseController extends ControllerBase {
final var courseRepository = this.repositories.getCourseRepository();
final var course = courseRepository.findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
final var successfullyAdded = course.addAssignmentId(aid);
logger.debug("Assignment {} added to course {} successfully: {}", aid, cid,
successfullyAdded);
@@ -239,6 +251,7 @@ public class CourseController extends ControllerBase {
final var courseRepository = this.repositories.getCourseRepository();
final var course = courseRepository.findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
try (var dc = this.db.connect()) {
final var user = dc.getUser(username).orElseThrow(() -> new MissingUserException(
@@ -274,6 +287,7 @@ public class CourseController extends ControllerBase {
throw new InvalidRoleException("Role must be one of ROLE_TA, ROLE_TEACHER");
}
final var course = this.repositories.getCourseRepository().findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
if (role.equals("ROLE_TA")) {
final var removed = this.securedService.removeTA(course, username);
logger.debug("Removed TA {} successfully: {}", username, removed);
@@ -301,6 +315,7 @@ public class CourseController extends ControllerBase {
final var res = new Response();
final var course = this.repositories.getCourseRepository().findExisting(cid);
+ this.securedService.checkForCourseAccess(course);
final var removed = course.removeAssignmentId(aid);
logger.debug("Removed assignment {} successfully: {}", aid, removed);
this.repositories.getCourseRepository().save(course);
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseSecuredService.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseSecuredService.java
index 1bea512fe1841e54358c26730ca85deb1d8cbd67..7e48d8d766edda0badf7df1c0a8d71622c9c3585 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseSecuredService.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/CourseSecuredService.java
@@ -1,12 +1,23 @@
package nl.tudelft.ewi.auta.core.controller;
+import nl.tudelft.ewi.auta.core.database.CourseRepository;
+import nl.tudelft.ewi.auta.core.database.EntityContainer;
+import nl.tudelft.ewi.auta.core.model.Assignment;
import nl.tudelft.ewi.auta.core.model.Course;
+import nl.tudelft.ewi.auta.core.model.Submission;
+import nl.tudelft.ewi.auta.core.response.exception.UserNotAuthorizedException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.security.access.annotation.Secured;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
/**
* A class that is needed for spring method-level security.
- *
+ * <p>
* Defining these methods in the controller results in spring ignoring all security.
* This is because Spring AOP Proxying is used to apply method-level security. If a secured method
* is called by another method in the same class, all method security is ignored.
@@ -14,9 +25,26 @@ import org.springframework.stereotype.Service;
@Service
public class CourseSecuredService {
+ private static final Logger logger = LoggerFactory.getLogger(CourseSecuredService.class);
+
/**
- * Adds an instructor to a course.
+ * The course repository used to look up course ids.
+ */
+ private final CourseRepository courseRepository;
+
+ /**
+ * Creates a new course secured service, which uses the course repository to manage access
+ * rights for different users.
*
+ * @param courseRepository the course repository where courses are stored.
+ */
+ public CourseSecuredService(final CourseRepository courseRepository) {
+ this.courseRepository = courseRepository;
+ }
+
+ /**
+ * Adds an instructor to a course.
+ * <p>
* Only admins should be able to do this.
*
* @param course the course the instructor is being added to
@@ -30,7 +58,7 @@ public class CourseSecuredService {
/**
* Adds a TA to a course.
- *
+ * <p>
* Both ADMINs and TEACHERs can do this.
*
* @param course the course the TA is being added to
@@ -44,7 +72,7 @@ public class CourseSecuredService {
/**
* Removes an instructor from a course.
- *
+ * <p>
* Only admins should be able to do this.
*
* @param course the course the instructor is being removed from
@@ -58,7 +86,7 @@ public class CourseSecuredService {
/**
* Removes a TA from a course.
- *
+ * <p>
* Both ADMINs and TEACHERs can do this.
*
* @param course the course the TA is being removed from
@@ -70,4 +98,163 @@ public class CourseSecuredService {
return course.removeTA(username);
}
+ /**
+ * Checks if a user has access to an assignment by checking for access to the courses the
+ * assignment is registered to.
+ *
+ * @param authentication the {@link Authentication} to use.
+ * @param aid the assignment id
+ * @return {@code true} if the user has access to any of the courses the assignment belongs
+ * to, or the user is an admin
+ */
+ public boolean userHasAssignmentAccess(final Authentication authentication, final String aid) {
+ final var userDetails = (UserDetails) authentication.getPrincipal();
+ final var username = userDetails.getUsername();
+ logger.trace("Checking assignment access for user {}, cid {}", username, aid);
+ if (this.userIsAdmin(userDetails)) {
+ return true;
+ }
+ final var courseList = this.courseRepository.findByAssignmentIdsContaining(aid);
+ return courseList.stream().anyMatch(course
+ -> this.userHasCourseAccess(authentication, course));
+ }
+
+ /**
+ * Checks if a user has access to an assignment by checking for access to the courses the
+ * assignment is registered to.
+ *
+ * @param authentication the {@link Authentication} to use.
+ * @param assignment the assignment
+ * @return {@code true} if the user has access to any of the courses the assignment belongs
+ * to, or the user is an admin
+ */
+ public boolean userHasAssignmentAccess(final Authentication authentication,
+ final Assignment assignment) {
+ return this.userHasAssignmentAccess(authentication, assignment.getId());
+ }
+
+ /**
+ * Checks if a user has an admin role.
+ * @param userDetails the user details object that belongs to the user
+ * @return true if the user is an admin, else returns false
+ */
+ private boolean userIsAdmin(final UserDetails userDetails) {
+ return userDetails.getAuthorities().stream()
+ .map(GrantedAuthority::getAuthority)
+ .anyMatch(authority -> authority.equals("ROLE_ADMIN"));
+ }
+
+ /**
+ * Checks if a user has access to a course by looking for the username in the course's TA or
+ * instructor set.
+ *
+ * @param authentication the {@link Authentication} to use.
+ * @param course the {@link Course} to use
+ * @return {@code false} if there is no such course, or the user is not a TA or instructor in
+ * that course. Otherwise, returns {@code true}. If the user is an admin, always returns
+ * {@code true}.
+ */
+ public boolean userHasCourseAccess(final Authentication authentication, final Course course) {
+
+ final var userDetails = (UserDetails) authentication.getPrincipal();
+ final var username = userDetails.getUsername();
+ if (this.userIsAdmin(userDetails)) {
+ return true;
+ }
+ final var cid = course.getId();
+ logger.debug("Checking course access for user {}, cid {}", username, cid);
+ if (course.getTaSet().contains(username)) {
+ logger.debug("User {} is registered as a TA in course {}", username, cid);
+ return true;
+ }
+
+ if (course.getInstructorSet().contains(username)) {
+ logger.debug("User {} is registered as an instructor in course {}", username, cid);
+ return true;
+ }
+
+ logger.debug("User {} does not have access to course {}", username, cid);
+ return false;
+ }
+
+ /**
+ * Checks if a user has access to a submission by checking if they have access to the parent
+ * assignment.
+ *
+ * @param authentication the {@link Authentication} objec tassociated with the user.
+ * @param submission the submission to check access for
+ * @return {@code true} if the user has access, else {@code false}.
+ */
+ public boolean userHasSubmissionAccess(final Authentication authentication,
+ final Submission submission) {
+ assert submission.getAssignmentId() != null;
+ return this.userHasAssignmentAccess(authentication, submission.getAssignmentId());
+ }
+
+ /**
+ * Checks if access to a course is allowed in the current security context.
+ *
+ * @param course the {@link Course} that will be checked.
+ *
+ * @throws UserNotAuthorizedException if access to the course is not allowed in the current
+ * security context.
+ */
+ public void checkForCourseAccess(final Course course) {
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (!this.userHasCourseAccess(authentication, course)) {
+ throw new UserNotAuthorizedException("User is not authorized to access course: "
+ + course.getId());
+ }
+ }
+
+ /**
+ * Checks if access to an assignment is allowed in the current security context.
+ *
+ * @param assignment the {@link Assignment} that will be checked.
+ *
+ * @throws UserNotAuthorizedException if access to the assignment is not allowed in the current
+ * security context.
+ */
+ public void checkForAssignmentAccess(final Assignment assignment) {
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (!this.userHasAssignmentAccess(authentication, assignment)) {
+ throw new UserNotAuthorizedException("User is not authorized to access assignment: "
+ + assignment.getId());
+ }
+ }
+
+
+ /**
+ * Checks if access to a submission is allowed in the current security context.
+ *
+ * @param submission the {@link Submission} that will be checked.
+ *
+ * @throws UserNotAuthorizedException if access to the submission is not allowed in the current
+ * security context.
+ */
+ public void checkForSubmissionAccess(final Submission submission) {
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (!this.userHasSubmissionAccess(authentication, submission)) {
+ throw new UserNotAuthorizedException("User is not authorized to access submission: "
+ + submission.getId());
+ }
+ }
+
+
+ /**
+ * Checks if access to an entity container is allowed in the current security context.
+ *
+ * @param container the {@link EntityContainer} that will be checked.
+ *
+ * @throws UserNotAuthorizedException if access to the container is not allowed in the current
+ * security context.
+ */
+
+ public void checkForEntityContainerAccess(final EntityContainer container) {
+ final var authentication = SecurityContextHolder.getContext().getAuthentication();
+ if (!this.userHasAssignmentAccess(authentication, container.getAssignmentId())) {
+ throw new UserNotAuthorizedException("User is not authorized to access container: "
+ + container.getId());
+ }
+ }
}
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/ReportController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/ReportController.java
index aec7046ac6cfe69d10646dd4bdd399fdefada61f..561e43c50eee73640b0fe4929a6a5990bf34b080 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/ReportController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/ReportController.java
@@ -18,13 +18,20 @@ public class ReportController extends ControllerBase {
*/
private final Repositories repositories;
+ /**
+ * The service that only prevents unauthorized users from accessing information.
+ */
+ private final CourseSecuredService securedService;
+
/**
* Creates a new report controller.
*
* @param repositories the repositories
*/
- public ReportController(final Repositories repositories) {
+ public ReportController(final Repositories repositories,
+ final CourseSecuredService securedService) {
this.repositories = repositories;
+ this.securedService = securedService;
}
@GetMapping(
@@ -36,7 +43,9 @@ public class ReportController extends ControllerBase {
final var res = new Response();
this.repositories.getAssignmentRepository().findExisting(aid);
- this.repositories.getSubmissionRepository().findExisting(aid, sid);
+ final var submission = this.repositories.getSubmissionRepository().findExisting(aid, sid);
+ this.securedService.checkForSubmissionAccess(submission);
+
final var entityContainerOptional = this.repositories.getEntityRepository()
.findByParentIds(sid, aid);
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionController.java
index 669ac485e2b509312874e7e7f7166d1338dd8440..ee7aae21a2ed5198480e4aaf89a8b9845de68843 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionController.java
@@ -1,23 +1,15 @@
package nl.tudelft.ewi.auta.core.controller;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.stream.Collectors;
-
import freemarker.template.TemplateException;
import nl.tudelft.ewi.auta.core.database.EntityContainer;
import nl.tudelft.ewi.auta.core.database.IdentityContainer;
import nl.tudelft.ewi.auta.core.database.Repositories;
+import nl.tudelft.ewi.auta.core.jobs.JobQueue;
import nl.tudelft.ewi.auta.core.model.FileStore;
import nl.tudelft.ewi.auta.core.model.Job;
import nl.tudelft.ewi.auta.core.model.Submission;
import nl.tudelft.ewi.auta.core.report.HtmlReportGenerator;
+import nl.tudelft.ewi.auta.core.response.Response;
import nl.tudelft.ewi.auta.core.response.exception.InvalidFileTypeException;
import nl.tudelft.ewi.auta.core.response.exception.InvalidSubmissionNameException;
import org.slf4j.Logger;
@@ -31,13 +23,19 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
-
-import nl.tudelft.ewi.auta.core.jobs.JobQueue;
-import nl.tudelft.ewi.auta.core.response.Response;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
/**
* The controller serving endpoints related to submissions.
@@ -73,6 +71,11 @@ public class SubmissionController extends ControllerBase {
*/
private HtmlReportGenerator htmlReportGenerator;
+ /**
+ * The service used to make sure only authorized users can access submissions.
+ */
+ private final CourseSecuredService securedService;
+
/**
* The repositories.
*/
@@ -88,12 +91,14 @@ public class SubmissionController extends ControllerBase {
public SubmissionController(final JobQueue queue,
final FileStore files,
final HtmlReportGenerator htmlReportGenerator,
- final Repositories repositories
+ final Repositories repositories,
+ final CourseSecuredService securedService
) {
this.repositories = repositories;
this.queue = queue;
this.files = files;
this.htmlReportGenerator = htmlReportGenerator;
+ this.securedService = securedService;
}
/**
@@ -110,12 +115,17 @@ public class SubmissionController extends ControllerBase {
*
* @param aid the assignment id used to fetch the assignment
* @return list of submissions for given assignments.
+ *
+ * @throws nl.tudelft.ewi.auta.core.response.exception.UserNotAuthorizedException if the
+ * user is not allowed to access the assignment
+ * @throws nl.tudelft.ewi.auta.core.response.exception.NoSuchAssignmentException if the
+ * assignment could not be found
*/
@GetMapping(
path = "/api/v1/assignment/{aid}/submission",
produces = MediaType.APPLICATION_JSON_VALUE
)
- private ResponseEntity<Response> getAllSubmissions(
+ public ResponseEntity<Response> getAllSubmissions(
final @PathVariable String aid,
final @RequestParam(value = "page", required = false) Optional<Integer> pageNum,
final @RequestParam(value = "size", required = false) Optional<Integer> pageSize
@@ -127,6 +137,8 @@ public class SubmissionController extends ControllerBase {
final var pageReq = PageRequest.of(pageIndex, numPerPage);
final var identityRepository = this.repositories.getIdentityRepository();
+ final var assignment = this.repositories.getAssignmentRepository().findExisting(aid);
+ this.securedService.checkForAssignmentAccess(assignment);
final var page = this.repositories.getSubmissionRepository()
.findByAssignmentId(aid, pageReq);
@@ -156,6 +168,11 @@ public class SubmissionController extends ControllerBase {
* @param aid the identifier of the assignment the submission should be added to
* @param req the request body
* @return the response
+ *
+ * @throws nl.tudelft.ewi.auta.core.response.exception.NoSuchAssignmentException if the
+ * assignment could not be found
+ * @throws nl.tudelft.ewi.auta.core.response.exception.UserNotAuthorizedException if the user
+ * has no access to the submission
*/
@PostMapping(
value = "/api/v1/assignment/{aid}/submission",
@@ -170,6 +187,7 @@ public class SubmissionController extends ControllerBase {
final var submissionRepository = this.repositories.getSubmissionRepository();
final var assignment = assignmentRepository.findExisting(aid);
+ this.securedService.checkForAssignmentAccess(assignment);
final var name = this.getString(req, "name");
if (name.isEmpty() || name.length() > MAX_FIELD_SIZE) {
@@ -235,6 +253,7 @@ public class SubmissionController extends ControllerBase {
assignmentRepository.findExisting(aid);
final var submission = submissionRepository.findExisting(aid, sid);
+ this.securedService.checkForSubmissionAccess(submission);
res.put("id", submission.getId());
res.put("aid", submission.getAssignmentId());
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportController.java b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportController.java
index 25fd65256223ebc0aa4e6d57f1af48507cce2da7..6197ad81bc6ade57a143add779029564d71bcdc8 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportController.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportController.java
@@ -120,6 +120,11 @@ public class SubmissionExportController {
*/
private final MongoTemplate mongo;
+ /**
+ * A service that prevents unauthorized users from accessing endpoints.
+ */
+ private final CourseSecuredService securedService;
+
/**
* Creates a new assignment export controller.
*
@@ -130,11 +135,13 @@ public class SubmissionExportController {
public SubmissionExportController(
final Repositories repositories,
final Gson gson,
- final MongoTemplate mongo
+ final MongoTemplate mongo,
+ final CourseSecuredService securedService
) {
this.repositories = repositories;
this.gson = gson;
this.mongo = mongo;
+ this.securedService = securedService;
}
/**
@@ -248,13 +255,17 @@ public class SubmissionExportController {
*
* @throws nl.tudelft.ewi.auta.core.response.exception.NoSuchAssignmentException if the
* assignment does not exist
+ * @throws nl.tudelft.ewi.auta.core.response.exception.UserNotAuthorizedException if the user
+ * does not have access to the assignment
*/
@Contract("null -> null; !null -> !null")
@Nullable
private Assignment findAssignment(final @Nullable String aid) {
if (aid != null) {
final var assignmentRepository = this.repositories.getAssignmentRepository();
- return assignmentRepository.findExisting(aid);
+ final var assignment = assignmentRepository.findExisting(aid);
+ this.securedService.checkForAssignmentAccess(assignment);
+ return assignment;
} else {
return null;
}
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/database/CourseRepository.java b/core/src/main/java/nl/tudelft/ewi/auta/core/database/CourseRepository.java
index dc9e275e06fef38aa5c9774fdb974bdf3da5ba04..90ddc5c147646e1dc772fb18630ed2074dc2ac89 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/database/CourseRepository.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/database/CourseRepository.java
@@ -5,11 +5,22 @@ import nl.tudelft.ewi.auta.core.response.exception.NoSuchCourseException;
import org.springframework.data.mongodb.repository.MongoRepository;
import org.springframework.stereotype.Repository;
+import java.util.List;
import java.util.Optional;
@Repository
public interface CourseRepository extends MongoRepository<Course, String> {
+
+ /**
+ * Finds all courses that contain an assignment id.
+ *
+ * @param assignmentId the assignment id to look for
+ * @return the list of courses that contain the assignment id
+ */
+ List<Course> findByAssignmentIdsContaining(String assignmentId);
+
+
/**
* Finds the course by course code + year combination.
*
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/database/MongoConfig.java b/core/src/main/java/nl/tudelft/ewi/auta/core/database/MongoConfig.java
index fd5618f5fec0a8b936f50921ccf1d9317484e106..134b8cc86a57db4643a2d7c26be6b82303c223fb 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/database/MongoConfig.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/database/MongoConfig.java
@@ -2,6 +2,7 @@ package nl.tudelft.ewi.auta.core.database;
import nl.tudelft.ewi.auta.core.settings.GlobalSettings;
import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
import org.springframework.data.mongodb.config.AbstractMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
@@ -13,6 +14,7 @@ import javax.annotation.Nonnull;
* The database connection object.
*/
@Configuration
+@Profile("!test")
@EnableMongoRepositories(basePackages = "nl.tudelft.ewi.auta.core.database")
public class MongoConfig extends AbstractMongoConfiguration {
/**
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/report/GenericReportGenerator.java b/core/src/main/java/nl/tudelft/ewi/auta/core/report/GenericReportGenerator.java
index 29866c61ee755e0092386ad5643b90a8ca7ca0f4..22e26ca84710647471218fe39775bc9554dd27b0 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/report/GenericReportGenerator.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/report/GenericReportGenerator.java
@@ -92,7 +92,7 @@ public abstract class GenericReportGenerator {
*/
private Map<String, List<String>> aggregateNotesBySeverity(final ProjectEntity projectEntity,
final Severity severity) {
- final var allChildren = projectEntity.getAllChildren();
+ final var allChildren = projectEntity.getAllEntities();
final var noteMap = new HashMap<String, List<String>>();
allChildren.forEach(
c -> {
@@ -121,7 +121,7 @@ public abstract class GenericReportGenerator {
* @return the aggregated map of tips.
*/
private Map<String, List<String>> aggregateTips(final ProjectEntity projectEntity) {
- final var allChildren = projectEntity.getAllChildren();
+ final var allChildren = projectEntity.getAllEntities();
final var tipMap = new HashMap<String, List<String>>();
allChildren.forEach(
c -> {
diff --git a/core/src/main/java/nl/tudelft/ewi/auta/core/response/ErrorCode.java b/core/src/main/java/nl/tudelft/ewi/auta/core/response/ErrorCode.java
index 5c66c277e7ce7e95c059f887d747c3c9d8098260..ec512f1ed0e6678fb118b9ef6d373c76f2299a61 100644
--- a/core/src/main/java/nl/tudelft/ewi/auta/core/response/ErrorCode.java
+++ b/core/src/main/java/nl/tudelft/ewi/auta/core/response/ErrorCode.java
@@ -148,7 +148,7 @@ public enum ErrorCode {
* When the client tries to do they are not allowed to do (such as add a user to a course
* while they do not have high enough authority).
*/
- FORBIDDEN(HttpStatus.BAD_REQUEST),
+ FORBIDDEN(HttpStatus.FORBIDDEN),
/**
* If the client attempted to authenticate using an unknown SAML identity provider.
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/AbstractIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/AbstractIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..caed5119b0e033e901914c5f2a01c0b37391e1ef
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/AbstractIntegrationTest.java
@@ -0,0 +1,40 @@
+package nl.tudelft.ewi.auta.core;
+
+
+import nl.tudelft.ewi.auta.core.report.ScriptExecutor;
+import nl.tudelft.ewi.auta.core.response.OptionalSerializer;
+import nl.tudelft.ewi.auta.srf.iface.ScriptExecutionContextFactory;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+
+@ActiveProfiles("test")
+@SpringBootTest
+@ExtendWith(SpringExtension.class)
+public abstract class AbstractIntegrationTest {
+
+ @MockBean
+ ScriptExecutor executor;
+
+ // The ScriptExecutor and ScriptExecutionContextFactory are both mocked, as this is the
+ // easiest way to get the SpringBootTest working.
+ @MockBean
+ ScriptExecutionContextFactory factory;
+
+ @Configuration
+ @Import(Core.class)
+ public static class TestConfig {
+ @Bean
+ public OptionalSerializer serializer() {
+ // This bean needs to be declared seperately, as spring is unable to instantiate it
+ // for some reason.
+ return new OptionalSerializer();
+ }
+ }
+
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AbstractWebSecurityIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AbstractWebSecurityIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..24eef28bef2a683cb206dd8fcd2e83ae556dbb39
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AbstractWebSecurityIntegrationTest.java
@@ -0,0 +1,169 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import com.google.gson.Gson;
+import nl.tudelft.ewi.auta.common.model.entity.ProjectEntity;
+import nl.tudelft.ewi.auta.core.AbstractIntegrationTest;
+import nl.tudelft.ewi.auta.core.database.AssignmentRepository;
+import nl.tudelft.ewi.auta.core.database.CourseRepository;
+import nl.tudelft.ewi.auta.core.database.EntityContainer;
+import nl.tudelft.ewi.auta.core.database.EntityRepository;
+import nl.tudelft.ewi.auta.core.database.IdentityContainer;
+import nl.tudelft.ewi.auta.core.database.IdentityRepository;
+import nl.tudelft.ewi.auta.core.database.SubmissionRepository;
+import nl.tudelft.ewi.auta.core.model.Assignment;
+import nl.tudelft.ewi.auta.core.model.Course;
+import nl.tudelft.ewi.auta.core.model.Submission;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.MediaType;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.ResultActions;
+import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+import org.springframework.web.context.WebApplicationContext;
+
+import java.util.Set;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+public abstract class AbstractWebSecurityIntegrationTest extends AbstractIntegrationTest {
+
+ protected MockMvc mockMvc;
+ private Gson gson = new Gson();
+
+ @Autowired
+ private WebApplicationContext context;
+
+ @BeforeAll
+ public static void setupClass(@Autowired final AssignmentRepository assignmentRepository,
+ @Autowired final CourseRepository courseRepository,
+ @Autowired final SubmissionRepository submissionRepository,
+ @Autowired final IdentityRepository identityRepository,
+ @Autowired final EntityRepository entityRepository) {
+ populateDatabase(assignmentRepository, courseRepository, submissionRepository,
+ identityRepository, entityRepository);
+ }
+
+ @BeforeEach
+ public void setUp() {
+ this.mockMvc = MockMvcBuilders.webAppContextSetup(this.context)
+ .build();
+ }
+
+ protected MockHttpServletRequestBuilder withJsonBody(
+ final MockHttpServletRequestBuilder request,
+ final Object content) {
+ return request.content(this.gson.toJson(content))
+ .contentType(MediaType.APPLICATION_JSON);
+ }
+
+ public ResultActions getForbidden(final String url) throws Exception {
+ return this.mockMvc.perform(get(url)).andExpect(status().isForbidden());
+ }
+
+ public ResultActions deleteForbidden(final String url) throws Exception {
+ return this.mockMvc.perform(delete(url)).andExpect(status().isForbidden());
+ }
+
+ public ResultActions postForbidden(final String url, final Object content) throws Exception {
+ return this.mockMvc.perform(this.withJsonBody(post(url), content))
+ .andExpect(status().isForbidden());
+ }
+
+ public ResultActions putForbidden(final String url, final Object content) throws Exception {
+ return this.mockMvc.perform(this.withJsonBody(put(url), content))
+ .andExpect(status().isForbidden());
+ }
+
+ public ResultActions postExpectOk(final String url, final Object content) throws Exception {
+ return this.mockMvc.perform(this.withJsonBody(post(url), content))
+ .andExpect(status().isOk());
+ }
+
+
+ public ResultActions getExpectOk(final String url) throws Exception {
+ return this.mockMvc.perform(get(url)).andExpect(status().isOk());
+ }
+
+ public static void populateDatabase(final AssignmentRepository assignmentRepository,
+ final CourseRepository courseRepository,
+ final SubmissionRepository submissionRepository,
+ final IdentityRepository identityRepository,
+ final EntityRepository entityRepository) {
+ final var assignment1 = new Assignment();
+ assignment1.setId("assignment1");
+ assignment1.setName("assignment1");
+ assignment1.setAllowedLanguages(Set.of("java"));
+ assignmentRepository.save(assignment1);
+
+ final var assignment2 = new Assignment();
+ assignment2.setName("assignment2");
+ assignment2.setId("assignment2");
+ assignment2.setAllowedLanguages(Set.of("java"));
+ assignmentRepository.save(assignment2);
+
+ final var assignment3 = new Assignment();
+ assignment3.setName("assignment3");
+ assignment3.setId("assignment3");
+ assignment3.setAllowedLanguages(Set.of("java"));
+ assignmentRepository.save(assignment3);
+
+ final var course = new Course();
+ course.setCourseCode("AB1234");
+ course.setName("NO_TA_NO_INSTRUCTORS");
+ course.setId("course1");
+ course.addAssignmentId("assignment1");
+ courseRepository.save(course);
+
+ final var course2 = new Course();
+ course2.setCourseCode("AB1234");
+ course2.setName("TA_AND_INSTRUCTOR");
+ course2.addInstructor("instructor");
+ course2.setId("course2");
+ course2.addTA("ta");
+ course2.addAssignmentId("assignment2");
+ courseRepository.save(course2);
+
+ final var course3 = new Course();
+ course3.setCourseCode("AB1234_2");
+ course3.setName("TA_AND_INSTRUCTOR2");
+ course3.addInstructor("instructor");
+ course3.setId("course3");
+ course3.addTA("ta");
+ course3.addAssignmentId("assignment3");
+ courseRepository.save(course3);
+
+ final var submission = new Submission();
+ submission.setName("submission1");
+ submission.setId("submission1");
+ submission.setAssignmentId("assignment1");
+ submissionRepository.save(submission);
+
+ final var submission2 = new Submission();
+ submission2.setName("submission2");
+ submission2.setId("submission2");
+ submission2.setAssignmentId("assignment2");
+ submissionRepository.save(submission2);
+
+ final var identity = new IdentityContainer("submission1", "id1");
+ identityRepository.save(identity);
+ final var identity2 = new IdentityContainer("submission2", "id2");
+ identityRepository.save(identity2);
+
+ final var container1 = new EntityContainer(new ProjectEntity(), "submission1", false, null,
+ "assignment1");
+ container1.setId("container1");
+ entityRepository.save(container1);
+
+ final var container2 = new EntityContainer(new ProjectEntity(), "submission2", false, null,
+ "assignment2");
+ container2.setId("container2");
+ entityRepository.save(container2);
+ }
+
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..2ad11b5c75dc67f21ca0b84fd93237016b386d31
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerIntegrationTest.java
@@ -0,0 +1,61 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.http.HttpMethod;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import java.util.Map;
+
+import static org.hamcrest.Matchers.hasSize;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@EnableAutoConfiguration
+public class AssignmentControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @Test
+ @WithMockUser(username = "admin", roles = "ADMIN")
+ public void adminCanViewAllAssignments() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/assignment"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.assignments", hasSize(3)));
+ }
+
+ @Test
+ @WithMockUser(username = "admin", roles = "ADMIN")
+ public void adminCanViewSinglelAssignment() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/assignment/assignment1"))
+ .andExpect(status().isOk());
+ }
+
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotViewAllCourses() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/assignment"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.assignments", hasSize(2)));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAccessUnautorizedCourse() throws Exception {
+ this.getForbidden("/api/v1/assignment/assignment1");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotDeleteUnautorizedCourse() throws Exception {
+ this.deleteForbidden("/api/v1/assignment/assignment1");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotUpdateUnautorizedCourse() throws Exception {
+ this.putForbidden("/api/v1/assignment/assignment1",
+ Map.of("name", "test", "language", "java"));
+ }
+}
+
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerTest.java
index 8a22c5f58d300ab52432b65c0984a569ec6d3d6d..95d831d140fef6215a0054d07c9748a6d8b52de4 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/AssignmentControllerTest.java
@@ -1,18 +1,8 @@
package nl.tudelft.ewi.auta.core.controller;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.hamcrest.Matchers.contains;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.hasSize;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
-import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
-import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
-
-import java.util.Optional;
-
+import nl.tudelft.ewi.auta.core.database.AssignmentRepository;
+import nl.tudelft.ewi.auta.core.model.Assignment;
+import nl.tudelft.ewi.auta.core.model.Submission;
import nl.tudelft.ewi.auta.core.response.exception.NoSuchAssignmentException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -26,17 +16,28 @@ import org.springframework.http.HttpMethod;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
-import nl.tudelft.ewi.auta.core.database.AssignmentRepository;
-import nl.tudelft.ewi.auta.core.model.Assignment;
-import nl.tudelft.ewi.auta.core.model.Submission;
+import java.util.Optional;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public class AssignmentControllerTest {
+ private static final String ID = "UNIQUE_ID";
private MockMvc mvc;
-
@Mock
private AssignmentRepository assignmentStore;
- private static final String ID = "UNIQUE_ID";
+ @Mock
+ private CourseSecuredService securedService;
@InjectMocks
private AssignmentController controller;
@@ -48,15 +49,15 @@ public class AssignmentControllerTest {
MockitoAnnotations.initMocks(this);
final Answer<Assignment> storeAnswer = invocation -> {
- Assignment assignmt = invocation.getArgument(0);
+ final Assignment assignmt = invocation.getArgument(0);
assignmt.setId(ID);
return assignmt;
};
- Mockito.when(this.assignmentStore.save(Mockito.any(Assignment.class)))
+ Mockito.when(this.assignmentStore.save(any(Assignment.class)))
.thenAnswer(storeAnswer);
- Mockito.when(this.assignmentStore.insert(Mockito.any(Assignment.class)))
+ Mockito.when(this.assignmentStore.insert(any(Assignment.class)))
.thenAnswer(storeAnswer);
this.assignment = new Assignment("name");
@@ -72,14 +73,14 @@ public class AssignmentControllerTest {
this.assignment.addSubmission(submission1);
this.assignment.addSubmission(submission2);
- Mockito.when(this.assignmentStore.findExisting(Mockito.any(String.class)))
+ Mockito.when(this.assignmentStore.findExisting(any(String.class)))
.thenThrow(new NoSuchAssignmentException("not found"));
Mockito.when(this.assignmentStore.findExisting(Mockito.eq("existing")))
.thenReturn(this.assignment);
this.mvc = MockMvcBuilders.standaloneSetup(this.controller)
- .setControllerAdvice(new RestExceptionHandler())
- .build();
+ .setControllerAdvice(new RestExceptionHandler())
+ .build();
}
@Test
@@ -111,7 +112,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.id", equalTo(ID)));
Mockito.verify(this.assignmentStore, Mockito.times(1))
- .insert(Mockito.any(Assignment.class));
+ .insert(any(Assignment.class));
}
@Test
@@ -124,7 +125,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("MISSING_FIELD")));
- Mockito.verify(this.assignmentStore, Mockito.never()).insert(Mockito.any(Assignment.class));
+ Mockito.verify(this.assignmentStore, Mockito.never()).insert(any(Assignment.class));
}
@Test
@@ -137,7 +138,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("INVALID_ASSIGNMENT_NAME")));
- Mockito.verify(this.assignmentStore, Mockito.never()).insert(Mockito.any(Assignment.class));
+ Mockito.verify(this.assignmentStore, Mockito.never()).insert(any(Assignment.class));
}
@Test
@@ -150,12 +151,12 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("INVALID_LANGUAGE")));
- Mockito.verify(this.assignmentStore, Mockito.never()).insert(Mockito.any(Assignment.class));
+ Mockito.verify(this.assignmentStore, Mockito.never()).insert(any(Assignment.class));
}
@Test
public void testCreateDuplicateName() throws Exception {
- Mockito.when(this.assignmentStore.insert(Mockito.any(Assignment.class)))
+ Mockito.when(this.assignmentStore.insert(any(Assignment.class)))
.thenThrow(DuplicateKeyException.class);
this.mvc.perform(request(HttpMethod.POST, "/api/v1/assignment")
@@ -166,7 +167,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("ASSIGNMENT_ALREADY_EXISTS")));
- Mockito.verify(this.assignmentStore, Mockito.never()).save(Mockito.any());
+ Mockito.verify(this.assignmentStore, Mockito.never()).save(any());
}
@Test
@@ -180,7 +181,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.id", equalTo(ID)));
Mockito.verify(this.assignmentStore, Mockito.times(1))
- .save(Mockito.any(Assignment.class));
+ .save(any(Assignment.class));
}
@Test
@@ -193,12 +194,12 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("MISSING_FIELD")));
- Mockito.verify(this.assignmentStore, Mockito.never()).save(Mockito.any());
+ Mockito.verify(this.assignmentStore, Mockito.never()).save(any());
}
@Test
public void testUpdateDuplicateName() throws Exception {
- Mockito.when(this.assignmentStore.save(Mockito.any(Assignment.class)))
+ Mockito.when(this.assignmentStore.save(any(Assignment.class)))
.thenThrow(DuplicateKeyException.class);
this.mvc.perform(request(HttpMethod.PUT, "/api/v1/assignment/existing")
@@ -209,7 +210,7 @@ public class AssignmentControllerTest {
.andExpect(jsonPath("$.errors", hasSize(1)))
.andExpect(jsonPath("$.errors[0].code", equalTo("ASSIGNMENT_ALREADY_EXISTS")));
- Mockito.verify(this.assignmentStore, Mockito.times(1)).save(Mockito.any());
+ Mockito.verify(this.assignmentStore, Mockito.times(1)).save(any());
}
@Test
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..f61a21dfbcb0354098e96575e506792b03aa8d94
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerIntegrationTest.java
@@ -0,0 +1,42 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import nl.tudelft.ewi.auta.core.benchmarking.BenchmarkingData;
+import org.hamcrest.Matchers;
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@EnableAutoConfiguration
+public class BenchmarkingControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAccessUnautorizedSubmission() throws Exception {
+ this.postForbidden("/api/v1/assignment/assignment1/submission/submission1/benchmark/"
+ + "rank", new BenchmarkingData());
+
+ this.getForbidden("/api/v1/assignment/assignment1/submission/submission1/benchmark/"
+ + "score");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAccessAuthorizedSubmissionRank() throws Exception {
+ this.mockMvc.perform(this.withJsonBody(post("/api/v1/assignment/assignment2/"
+ + "submission/submission2/benchmark/"), new BenchmarkingData()))
+ .andExpect(status().is(Matchers.not(HttpStatus.FORBIDDEN.value())));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAccessAuthorizedSubmissionScore() throws Exception {
+ this.mockMvc.perform(get("/api/v1/assignment/assignment2/"
+ + "submission/submission2/benchmark/score"))
+ .andExpect(status().is(Matchers.not(HttpStatus.FORBIDDEN.value())));
+ }
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerTest.java
index f76412dd8002bafd6d940a87bccf0bb5ca3b57b0..c5d77a9551c0e7fbc14014768de45d2fd83ecd39 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/BenchmarkingControllerTest.java
@@ -67,6 +67,9 @@ public class BenchmarkingControllerTest extends JsonControllerTestHelper {
@Mock
private BadgeGenerator generator;
+ @Mock
+ CourseSecuredService securedService;
+
@InjectMocks
private BenchmarkingController controller;
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..357c030039acc93854bbca9c359b6e179013016d
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerIntegrationTest.java
@@ -0,0 +1,120 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import com.google.gson.Gson;
+import nl.tudelft.ewi.auta.core.database.CourseRepository;
+import nl.tudelft.ewi.auta.core.model.Course;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import java.util.Map;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@EnableAutoConfiguration
+public class CourseControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @Test
+ @WithMockUser(username = "admin", roles = "ADMIN")
+ public void adminCanViewAuthorizedCourse() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/course/course1"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.course.id", is("course1")));
+ }
+
+ @Test
+ @WithMockUser(username = "bob", roles = "ADMIN")
+ public void adminCanViewAllCoursesTest() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/course"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.courses", hasSize(3)));
+ }
+
+ @Test
+ @WithMockUser(username = "ta", roles = "TA")
+ public void taCannotViewAllCoursesTest() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/course"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.courses", hasSize(2)));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotViewAllCoursesTest() throws Exception {
+ this.mockMvc.perform(request(HttpMethod.GET, "/api/v1/course"))
+ .andExpect(status().isOk())
+ .andExpect(jsonPath("$.courses", hasSize(2)));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseGet() throws Exception {
+ this.getForbidden("/api/v1/course/course1");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseDelete() throws Exception {
+ this.deleteForbidden("/api/v1/course/course1");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseUpdateCourse() throws Exception {
+ final var newCourse = new Course();
+ newCourse.setYear(2025);
+ newCourse.setCourseCode("1234");
+ newCourse.setName("course name");
+ this.putForbidden("/api/v1/course/course1", newCourse);
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseAddUser() throws Exception {
+ this.putForbidden("/api/v1/course/course1/user",
+ Map.of("username", "instructor", "role", "ROLE_TEACHER"));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseDeleteUser() throws Exception {
+ this.deleteForbidden("/api/v1/course/course1/user/ROLE_TEACHER/instructor");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseAddAssignment() throws Exception {
+ this.putForbidden("/api/v1/course/course1/assignment", Map.of("aid",
+ "assignment1"));
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCanAccessAuthorizedCourseAddAssignment(
+ @Autowired final CourseRepository courseRepository
+ ) throws Exception {
+ this.mockMvc.perform(put("/api/v1/course/course2/assignment")
+ .content(new Gson().toJson(Map.of("aid", "assignment1")))
+ .contentType(MediaType.APPLICATION_JSON))
+ .andExpect(status().isOk());
+
+ final var course = courseRepository.findExisting("course2");
+ assertThat(course.getAssignmentIds()).contains("assignment1");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void userCannotAccessUnauthorizedCourseDeleteAssignment() throws Exception {
+ this.deleteForbidden("/api/v1/course/course1/assignment/aid");
+ }
+}
+
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerTest.java
index 0f4aa67a3fc5e84e4a478a31f9db6d692106f695..45c09e7b5a49d9413d920b7375e60a1b86a04b4f 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/CourseControllerTest.java
@@ -19,7 +19,6 @@ import org.junit.jupiter.params.provider.CsvSource;
import org.mockito.AdditionalMatchers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.Spy;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
@@ -39,6 +38,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -52,26 +53,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public class CourseControllerTest extends JsonControllerTestHelper {
+ private static final String CID = "cid";
+ private static final String AID = "aid";
+ private static final String AID_ALREADY_EXISTS = "alreadyExists";
private MockMvc mvc;
-
@Mock
private Repositories repositories;
-
@Mock
private CourseRepository courseRepository;
-
@Mock
private AssignmentRepository assignmentRepository;
-
@Mock
private DatabaseConnection databaseConnection;
+ @InjectMocks
@Spy
private CourseSecuredService securedService;
-
@Mock
private DatabaseConnector databaseConnector;
-
private Course course;
private AutaUser ta;
@@ -79,11 +78,6 @@ public class CourseControllerTest extends JsonControllerTestHelper {
private AutaUser admin;
private AutaUser student;
- private static final String CID = "cid";
- private static final String AID = "aid";
- private static final String AID_ALREADY_EXISTS = "alreadyExists";
-
- @InjectMocks
private CourseController controller;
@BeforeEach
@@ -91,9 +85,11 @@ public class CourseControllerTest extends JsonControllerTestHelper {
initMocks(this);
- //secured service
- this.securedService = new CourseSecuredService();
-
+ this.controller = new CourseController(this.repositories, this.databaseConnector,
+ this.securedService);
+ doNothing().when(this.securedService).checkForCourseAccess(any());
+ doReturn(true).when(this.securedService)
+ .userHasCourseAccess(any(), any(Course.class));
//courses
this.course = new Course();
this.course.setCourseCode("courseCode");
@@ -169,13 +165,13 @@ public class CourseControllerTest extends JsonControllerTestHelper {
@Test
public void addCourseDoesNotExistTest() throws Exception {
- var courseWithId = new Course();
+ final var courseWithId = new Course();
courseWithId.setId(CID);
when(this.courseRepository.findByCodeAndYear(eq("otherCourseCode"), eq(2019)))
.thenReturn(Optional.empty());
when(this.courseRepository.save(any())).thenReturn(courseWithId);
- var newCourse = new Course();
+ final var newCourse = new Course();
newCourse.setCourseCode("otherCourseCode");
newCourse.setName("course name");
newCourse.setYear(2019);
@@ -187,13 +183,13 @@ public class CourseControllerTest extends JsonControllerTestHelper {
.content(new Gson().toJson(newCourse)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.id", equalTo(CID)));
- verify(this.courseRepository, Mockito.atLeastOnce()).save(any());
+ verify(this.courseRepository, atLeastOnce()).save(any());
verify(this.courseRepository, never()).save(eq(this.course));
}
@Test
public void addCourseInvalidTest() throws Exception {
- var newCourse = new Course();
+ final var newCourse = new Course();
newCourse.setCourseCode("otherCourseCode");
newCourse.setName("course name");
//no year
@@ -209,7 +205,7 @@ public class CourseControllerTest extends JsonControllerTestHelper {
@Test
public void addCourseMissingNameTest() throws Exception {
- var newCourse = new Course();
+ final var newCourse = new Course();
newCourse.setCourseCode("otherCourseCode");
newCourse.setYear(2019);
// Serializes the course object, should already exist in the repository.
@@ -223,7 +219,7 @@ public class CourseControllerTest extends JsonControllerTestHelper {
@Test
public void addCourseMissingCourseCodeTest() throws Exception {
- var newCourse = new Course();
+ final var newCourse = new Course();
newCourse.setName("course name");
newCourse.setYear(2019);
//no year
@@ -239,7 +235,7 @@ public class CourseControllerTest extends JsonControllerTestHelper {
@Test
public void addCourseTooOldTest() throws Exception {
- var newCourse = new Course();
+ final var newCourse = new Course();
newCourse.setName("course name");
newCourse.setCourseCode("otherCourseCode");
newCourse.setYear(1835);
@@ -254,8 +250,6 @@ public class CourseControllerTest extends JsonControllerTestHelper {
}
-
-
//Adding users with too low authorities should fail
@ParameterizedTest
@CsvSource({"student, ROLE_TA", "student, ROLE_TEACHER", "ta, ROLE_TEACHER"})
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..e6035020f78c404b227b1b2c86ab11722c6ce4c8
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerIntegrationTest.java
@@ -0,0 +1,21 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.security.test.context.support.WithMockUser;
+
+@EnableAutoConfiguration
+public class ReportControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAccessUnauthorizedSubmission() throws Exception {
+ this.getForbidden("/api/v1/assignment/assignment1/submission/submission1/verdict");
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAccessAuthorizedSubmission() throws Exception {
+ this.getExpectOk("/api/v1/assignment/assignment2/submission/submission2/verdict");
+ }
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerTest.java
index a3fa32c3dc21ad5c9b6bbdbdae69ff49380a353a..7d793cba43f65c6ff5b02248b019aaf47e1a2449 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/ReportControllerTest.java
@@ -45,6 +45,9 @@ public class ReportControllerTest {
@Mock
private EntityRepository entityRepository;
+ @Mock
+ private CourseSecuredService securedService;
+
private static final String ID = "UNIQUE_ID";
@InjectMocks
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..5a7823093f394615b3fbf79281830228877c747e
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerIntegrationTest.java
@@ -0,0 +1,54 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.http.HttpStatus;
+import org.springframework.security.test.context.support.WithMockUser;
+
+import java.util.Map;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@EnableAutoConfiguration
+public class SubmissionControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "/api/v1/assignment/assignment1/submission",
+ "/api/v1/assignment/assignment1/submission/submission1"
+ })
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAccessUnauthorizedSubmission(final String input) throws Exception {
+ this.getForbidden(input);
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAddNewUnauthorizedSubmission() throws Exception {
+ this.postForbidden("/api/v1/assignment/assignment1/submission",
+ Map.of());
+ }
+
+ @Test
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAddNewSubmission() throws Exception {
+ this.mockMvc.perform(
+ this.withJsonBody(
+ post("/api/v1/assignment/assignment2/submission"),
+ Map.of("name", "new-submission")))
+ .andExpect(status().is(HttpStatus.CREATED.value()));
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "/api/v1/assignment/assignment2/submission",
+ "/api/v1/assignment/assignment2/submission/submission2"
+ })
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAccessAuthorizedSubmission(final String input) throws Exception {
+ this.getExpectOk(input);
+ }
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerTest.java
index 8eb452c0e16ea863b29801a0ea427301de0ae418..b7e8b818b701de587ffe227ca5c7813dd6f0860d 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionControllerTest.java
@@ -93,6 +93,8 @@ public class SubmissionControllerTest {
@Mock
private IdentityRepository identityRepository;
+ @Mock
+ CourseSecuredService securedService;
@InjectMocks
private SubmissionController controller;
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerIntegrationTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerIntegrationTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..d24e4c32cc4601239c0d8f6268d1c001ab8ade76
--- /dev/null
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerIntegrationTest.java
@@ -0,0 +1,32 @@
+package nl.tudelft.ewi.auta.core.controller;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.security.test.context.support.WithMockUser;
+
+@EnableAutoConfiguration
+public class SubmissionExportControllerIntegrationTest extends AbstractWebSecurityIntegrationTest {
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "/api/v1/assignment/assignment1/export",
+ "/api/v1/assignment/assignment1/export/id1"
+ })
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCannotAccessUnauthorizedSubmissionExport(final String input)
+ throws Exception {
+ this.getForbidden(input);
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = {
+ "/api/v1/assignment/assignment2/export",
+ "/api/v1/assignment/assignment2/export/id2"
+ })
+ @WithMockUser(username = "instructor", roles = "TEACHER")
+ public void instructorCanAccessAuthorizedSubmissionExport(final String input)
+ throws Exception {
+ this.getExpectOk(input);
+ }
+}
diff --git a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerTest.java b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerTest.java
index 27e399b49363fad7cfe03c1b411e5b8a9e5e7d12..b915bf315973facc278101353d5c5018970e340c 100644
--- a/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerTest.java
+++ b/core/src/test/java/nl/tudelft/ewi/auta/core/controller/SubmissionExportControllerTest.java
@@ -29,10 +29,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.any;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -77,12 +77,13 @@ public class SubmissionExportControllerTest {
private Assignment assignment;
+ private CourseSecuredService securedService;
private MockMvc mvc;
@BeforeEach
public void before() {
// Dependencies
-
+ this.securedService = mock(CourseSecuredService.class);
this.repositories = RepositoriesTestHelper.getMockRepositories();
this.gson = new Gson();
this.mongoTemplate = mock(MongoTemplate.class);
@@ -176,7 +177,7 @@ public class SubmissionExportControllerTest {
// Spring
this.mvc = MockMvcBuilders.standaloneSetup(new SubmissionExportController(
- this.repositories, this.gson, this.mongoTemplate
+ this.repositories, this.gson, this.mongoTemplate, this.securedService
))
.setControllerAdvice(new RestExceptionHandler())
.build();
diff --git a/core/src/test/resources/application.properties b/core/src/test/resources/application.properties
new file mode 100644
index 0000000000000000000000000000000000000000..c9297a6f5b877e799ba34a9a5232681f7ac1be78
--- /dev/null
+++ b/core/src/test/resources/application.properties
@@ -0,0 +1,9 @@
+logging.level.org.springframework.web=TRACE
+logging.level.org.springframework.boot.autoconfigure.mongo.embedded=TRACE
+logging.level.org.mongodb = DEBUG
+logging.file=test.log
+spring.servlet.multipart.max-file-size=128MB
+spring.servlet.multipart.max-request-size=128MB
+spring.data.mongodb.port=0
+spring.data.mongodb.database=auta-test
+spring.data.mongodb.host=localhost
diff --git a/src/main/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntity.java b/src/main/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntity.java
index 86a1d02b27b1f52bda18db5e8be1b328b826763d..2ec23b1ffcd371434f7d8a64d557778d89eacb98 100644
--- a/src/main/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntity.java
+++ b/src/main/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntity.java
@@ -1,7 +1,10 @@
package nl.tudelft.ewi.auta.common.model.entity;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Set;
public class ProjectEntity extends Entity {
@@ -82,4 +85,16 @@ public class ProjectEntity extends Entity {
}
this.addAll(toBeAdded);
}
+
+ /**
+ * Returns all entities in the project, including the project entity itself.
+ *
+ * @return all entities in the project
+ */
+ @JsonIgnore
+ public Set<Entity> getAllEntities() {
+ final var children = this.getAllChildren();
+ children.add(this);
+ return children;
+ }
}
diff --git a/src/test/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntityTest.java b/src/test/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntityTest.java
index df95acbb2aa4952cc413cb8491a5311eba3966fb..5bc97014ceab926aee3ea003b96e16e11326172f 100644
--- a/src/test/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntityTest.java
+++ b/src/test/java/nl/tudelft/ewi/auta/common/model/entity/ProjectEntityTest.java
@@ -83,4 +83,10 @@ public class ProjectEntityTest {
assertThat(recoverEntity).isEqualTo(this.projectEntity);
}
+
+ @Test
+ public void testGetAllEntitiesIncludesProject() {
+ final var entities = this.projectEntity.getAllEntities();
+ assertThat(entities).contains(this.projectEntity);
+ }
}