Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/project tests #48

Merged
merged 27 commits into from
Mar 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1701010
Added /api/users/{userId}
Aqua-sc Mar 4, 2024
63ea743
Updated msal config
usserwoutV2 Mar 4, 2024
5e84cd1
Started making role interceptor
usserwoutV2 Mar 4, 2024
a55bd1d
role test
usserwoutV2 Mar 4, 2024
a6bbdae
Fixed merge conflict
usserwoutV2 Mar 4, 2024
3b2ee90
Finished role interceptor
usserwoutV2 Mar 4, 2024
a0099db
Create a new user when new auth is detected
usserwoutV2 Mar 6, 2024
d45ef8b
Created filehandler
Aqua-sc Mar 6, 2024
391103d
Added route to save submission (not completed yet)
Aqua-sc Mar 6, 2024
4af54a6
Added persistent dir
Aqua-sc Mar 6, 2024
3c46daf
Finished role filter
usserwoutV2 Mar 6, 2024
5493611
Added db logic to saving submission
Aqua-sc Mar 6, 2024
8398c6e
Added route to fetch submission
Aqua-sc Mar 6, 2024
871ce4a
use Tika to check for zip file
Aqua-sc Mar 6, 2024
7131c80
submit:check to see if user is part of the project
Aqua-sc Mar 6, 2024
a9af18b
Merge pull request #39 from SELab-2/feature/savefiles
Matthias-VE Mar 7, 2024
45c0788
Merge remote-tracking branch 'origin/main' into feature/role-interceptor
usserwoutV2 Mar 7, 2024
80aec24
start of branch, not done yet
arnedierick Mar 7, 2024
2b0c01a
Added role dectorator de existing routes
usserwoutV2 Mar 7, 2024
8d4c190
Added timestamp when user is created
usserwoutV2 Mar 7, 2024
a2c57cd
Merge pull request #47 from SELab-2/feature/role-interceptor
usserwoutV2 Mar 7, 2024
ce1b297
added putmapping for project tests
arnedierick Mar 7, 2024
88e52f1
Merge branch 'main' into feature/project-tests
arnedierick Mar 8, 2024
d521620
code is opgekuist, pull request kan uitgevoerd worden mits goedkeuring
arnedierick Mar 8, 2024
7659d16
method for testpath
Aqua-sc Mar 8, 2024
3cd6a4a
updated dockerimage to string +fix db link
Aqua-sc Mar 8, 2024
5e1fdcd
re-enabled auth
Aqua-sc Mar 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,6 @@
package com.ugent.pidgeon.controllers;

public final class ApiRoutes {
public static final String USER_BASE_PATH = "/api/user/";
public static final String COURSE_BASE_PATH = "/api/course/";
}
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,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
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JpaProjectController {
@Autowired
private TestRepository testRepository;

@GetMapping("/api/projects")
@GetMapping("/api/temp/projects")
public List<String> getProjects() {
List<String> res = new ArrayList<>();
for (ProjectEntity project : projectRepository.findAll()) {
Expand Down
Loading
Loading