Skip to content

Commit

Permalink
Merge pull request #50 from SELab-2/feature/project_routes
Browse files Browse the repository at this point in the history
Feature/project routes
  • Loading branch information
AWerbrouck authored Mar 9, 2024
2 parents 1e4cdb3 + d261688 commit b2901ef
Show file tree
Hide file tree
Showing 38 changed files with 734 additions and 105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ out/

### VS Code ###
.vscode/
backend/app/data/*
2 changes: 2 additions & 0 deletions backend/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ dependencies {
implementation 'com.auth0:jwks-rsa:0.18.0'
implementation 'javax.servlet:javax.servlet-api:4.0.1'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.apache.tika:tika-core:1.27'
runtimeOnly 'org.postgresql:postgresql'


implementation "org.springframework.boot:spring-boot-devtools"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ugent.pidgeon.config;
package com.ugent.pidgeon.auth;

import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkException;
Expand Down Expand Up @@ -47,8 +47,8 @@ protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request,
String token = bearerToken.substring(7);

DecodedJWT jwt = JWT.decode(token);
Jwk jwk =null;
Algorithm algorithm=null;
Jwk jwk;
Algorithm algorithm;

try {
jwk = provider.get(jwt.getKeyId());
Expand All @@ -60,14 +60,13 @@ protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request,
String firstName = jwt.getClaim("given_name").asString();
String lastName = jwt.getClaim("family_name").asString();
String email = jwt.getClaim("unique_name").asString();
List<String> groups = jwt.getClaim("groups").asList(String.class);
String oid = jwt.getClaim("oid").asString();

// print full object
//logger.info(jwt.getClaims());
// logger.info(jwt.getClaims());


User user = new User(displayName, firstName,lastName, email, groups, oid);
User user = new User(displayName, firstName,lastName, email, oid);

Auth authUser = new Auth(user, new ArrayList<>());
SecurityContextHolder.getContext().setAuthentication(authUser);
Expand Down
14 changes: 14 additions & 0 deletions backend/app/src/main/java/com/ugent/pidgeon/auth/Roles.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.ugent.pidgeon.auth;

import com.ugent.pidgeon.postgre.models.types.UserRole;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Roles {
UserRole[] value();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.ugent.pidgeon.auth;


import com.ugent.pidgeon.model.Auth;
import com.ugent.pidgeon.postgre.models.UserEntity;
import com.ugent.pidgeon.postgre.models.types.UserRole;
import com.ugent.pidgeon.postgre.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.sql.Timestamp;
import java.util.Arrays;
import java.util.List;

@Component
public class RolesInterceptor implements HandlerInterceptor {


private final UserRepository userRepository;

@Autowired
public RolesInterceptor(UserRepository userRepository) {
this.userRepository = userRepository;
}


@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod handlerMethod) {
Roles rolesAnnotation = handlerMethod.getMethodAnnotation(Roles.class);
if (rolesAnnotation != null) {
List<UserRole> requiredRoles = Arrays.asList(rolesAnnotation.value());
Auth auth = (Auth) SecurityContextHolder.getContext().getAuthentication();
UserEntity userEntity = userRepository.findUserByAzureId(auth.getOid());

if(userEntity == null) {
System.out.println("User does not exist, creating new one");
userEntity = new UserEntity(auth.getUser().firstName,auth.getUser().lastName, auth.getEmail(), UserRole.student, auth.getOid());
Timestamp now = new Timestamp(System.currentTimeMillis());
userEntity.setCreatedAt(now);
userRepository.save(userEntity);
}
auth.setUserEntity(userEntity);

if (!requiredRoles.contains(userEntity.getRole()) || userEntity.getRole() == UserRole.admin) {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "User does not have required role");
return false;
}
}
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ugent.pidgeon.config;

import com.ugent.pidgeon.auth.JwtAuthenticationFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
Expand All @@ -22,7 +23,7 @@ public FilterRegistrationBean<JwtAuthenticationFilter> filterRegistrationBean()

FilterRegistrationBean<JwtAuthenticationFilter> filter = new FilterRegistrationBean<>();
filter.setFilter(new JwtAuthenticationFilter(tenantId));
filter.addUrlPatterns("/api/ietswatiknietwiltesten");
filter.addUrlPatterns("/api/*");
return filter;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
package com.ugent.pidgeon.config;

import com.ugent.pidgeon.auth.RolesInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {


private final RolesInterceptor rolesInterceptor;

@Autowired
public WebConfig(RolesInterceptor rolesInterceptor) {
this.rolesInterceptor = rolesInterceptor;
}

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("*")
.allowedOrigins("*")
.allowedHeaders("*");
}


@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(rolesInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.ugent.pidgeon.controllers;

public final class ApiRoutes {
public static final String USER_BASE_PATH = "/api/users";
public static final String COURSE_BASE_PATH = "/api/courses";

public static final String PROJECT_BASE_PATH = "/api/projects";
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ugent.pidgeon.controllers;
import com.ugent.pidgeon.auth.Roles;
import com.ugent.pidgeon.model.Auth;
import com.ugent.pidgeon.model.User;
import com.ugent.pidgeon.postgre.models.types.UserRole;
import com.ugent.pidgeon.postgre.repository.UserRepository;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -13,7 +15,9 @@ public class AuthTestController {
@Autowired
private UserRepository userRepository;


@GetMapping("/api/test")
@Roles({UserRole.student, UserRole.teacher})
public User testApi(HttpServletRequest request, Auth auth) {
return auth.getUser();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.ugent.pidgeon.controllers;

import com.ugent.pidgeon.auth.Roles;
import com.ugent.pidgeon.postgre.models.CourseEntity;
import com.ugent.pidgeon.postgre.models.ProjectEntity;
import com.ugent.pidgeon.postgre.models.types.UserRole;
import com.ugent.pidgeon.postgre.repository.CourseRepository;
import com.ugent.pidgeon.postgre.repository.ProjectRepository;
import com.ugent.pidgeon.postgre.repository.TestRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.Optional;

@RestController
public class CourseController {

@Autowired
private CourseRepository courseRepository;

@Autowired
private ProjectRepository projectRepository;

@Autowired
private TestRepository testRepository;

@GetMapping(ApiRoutes.COURSE_BASE_PATH + "/{courseId}")
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<CourseEntity> getCourseByCourseId(@PathVariable Long courseId) {
Optional<CourseEntity> courseopt = courseRepository.findById(courseId);
if (courseopt.isEmpty()) {
return ResponseEntity.notFound().build(); // Or return an empty list, based on your preference
}
CourseEntity course = courseopt.get();
return ResponseEntity.ok(course);
}

@GetMapping(ApiRoutes.COURSE_BASE_PATH + "/{courseId}/projects")
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<List<ProjectEntity>> getProjectByCourseId(@PathVariable Long courseId) {
List<ProjectEntity> projects = projectRepository.findByCourseId(courseId);
if (projects.isEmpty()) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(projects);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.ugent.pidgeon.controllers;

import com.ugent.pidgeon.auth.Roles;
import com.ugent.pidgeon.model.Auth;
import com.ugent.pidgeon.postgre.models.FileEntity;
import com.ugent.pidgeon.postgre.models.SubmissionEntity;
import com.ugent.pidgeon.postgre.models.types.UserRole;
import com.ugent.pidgeon.postgre.repository.FileRepository;
import com.ugent.pidgeon.postgre.repository.GroupRepository;
import com.ugent.pidgeon.postgre.repository.ProjectRepository;
import com.ugent.pidgeon.postgre.repository.SubmissionRepository;
import com.ugent.pidgeon.util.Filehandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.nio.file.Path;
import java.sql.Timestamp;

@RestController
public class FilesubmissiontestController {

@Autowired
private GroupRepository groupRepository;
@Autowired
private FileRepository fileRepository;
@Autowired
private SubmissionRepository submissionRepository;
@Autowired
private ProjectRepository projectRepository;

@PostMapping("/project/{projectid}/submit") //Route to submit a file, it accepts a multiform with the file and submissionTime
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<String> submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid,Auth auth) {
long userId = auth.getUserEntity().getId();
Long groupId = groupRepository.groupIdByProjectAndUser(projectid, userId);

if (!projectRepository.userPartOfProject(projectid, userId)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You aren't part of this project");
}
//TODO: executes the tests onces these are implemented
try {
//Save the file entry in the database to get the id
FileEntity fileEntity = new FileEntity("", "", userId);
long fileid = fileRepository.save(fileEntity).getId();

//Save the submission in the database TODO: update the accepted parameter
SubmissionEntity submissionEntity = new SubmissionEntity(projectid, groupId, fileid, time, false);
SubmissionEntity submission = submissionRepository.save(submissionEntity);

//Save the file on the server
Path path = Filehandler.getSubmissionPath(projectid, groupId, submission.getId());
String filename = Filehandler.saveSubmission(path, file);

//Update name and path for the file entry
fileEntity.setName(filename);
fileEntity.setPath(path.toString());
fileRepository.save(fileEntity);

return ResponseEntity.ok("File saved");
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage());
}

}

@GetMapping("submissions/{submissionid}")
@Roles({UserRole.teacher, UserRole.student})
public ResponseEntity<Resource> getSubmission(@PathVariable("submissionid") long submissionid, Auth auth) {
long userId = auth.getUserEntity().getId();
// Get the submission entry from the database
SubmissionEntity submission = submissionRepository.findById(submissionid).orElse(null);
if (submission == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}

if (!groupRepository.userInGroup(submission.getGroupId(), userId)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
}
// Get the file entry from the database
FileEntity file = fileRepository.findById(submission.getFileId()).orElse(null);
if (file == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
}


// Get the file from the server
try {
Resource zipFile = Filehandler.getSubmissionAsResource(Path.of(file.getPath(), file.getName()));

// Set headers for the response
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName());
headers.add(HttpHeaders.CONTENT_TYPE, "application/zip");

return ResponseEntity.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(zipFile);
} catch (Exception e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JpaCourseController {
@Autowired
private ProjectRepository projectRepository;

@GetMapping("/api/courses")
@GetMapping("/api/temp/courses")
public String getCourses() {
StringBuilder res = new StringBuilder();
for (CourseEntity course : courseRepository.findAll()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class JpaGroupController {
@Autowired
SubmissionRepository submissionRepository;

@GetMapping("/api/groups")
@GetMapping("/api/temp/groups")
public List<String> getGroups() {
List<String> res = new ArrayList<>();
for (GroupEntity group : groupRepository.findAll()) {
Expand Down
Loading

0 comments on commit b2901ef

Please sign in to comment.