From 1701010737b77327668ca890bdf9b50e08d805f7 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Mon, 4 Mar 2024 12:19:04 +0100 Subject: [PATCH 01/22] Added /api/users/{userId} --- .../ugent/pidgeon/controllers/ApiRoutes.java | 6 + .../controllers/AuthTestController.java | 2 +- .../controllers/JpaCourseController.java | 2 +- .../controllers/JpaGroupController.java | 2 +- .../controllers/JpaProjectController.java | 2 +- .../controllers/JpaSubmissionController.java | 2 +- .../controllers/JpaUserController.java | 2 +- .../pidgeon/controllers/UserController.java | 36 ++++++ .../model/json/CourseWithRelationJson.java | 33 ++++++ .../ugent/pidgeon/model/json/UserJson.java | 106 ++++++++++++++++++ .../postgre/repository/UserRepository.java | 8 ++ 11 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/model/json/CourseWithRelationJson.java create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/model/json/UserJson.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java new file mode 100644 index 00000000..9d6149a5 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/ApiRoutes.java @@ -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/"; +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java index 95536323..5229ce9a 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java @@ -12,7 +12,7 @@ public class AuthTestController { @Autowired private UserRepository userRepository; - @GetMapping("/api/test") + @GetMapping("/api/temp/test") public User testApi(HttpServletRequest request, Auth auth) { return auth.getUser(); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaCourseController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaCourseController.java index 7991cf6f..29a0545f 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaCourseController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaCourseController.java @@ -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()) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaGroupController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaGroupController.java index b3845a87..032b87ca 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaGroupController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaGroupController.java @@ -26,7 +26,7 @@ public class JpaGroupController { @Autowired SubmissionRepository submissionRepository; - @GetMapping("/api/groups") + @GetMapping("/api/temp/groups") public List getGroups() { List res = new ArrayList<>(); for (GroupEntity group : groupRepository.findAll()) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectController.java index eb245d3d..a3cf9957 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectController.java @@ -21,7 +21,7 @@ public class JpaProjectController { @Autowired private TestRepository testRepository; - @GetMapping("/api/projects") + @GetMapping("/api/temp/projects") public List getProjects() { List res = new ArrayList<>(); for (ProjectEntity project : projectRepository.findAll()) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaSubmissionController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaSubmissionController.java index 4fabad41..e63b7ed7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaSubmissionController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaSubmissionController.java @@ -20,7 +20,7 @@ public class JpaSubmissionController { @Autowired private FileRepository fileRepository; - @GetMapping("/api/submissions") + @GetMapping("/api/temp/submissions") public List getSubmissions() { List res = new ArrayList<>(); for (SubmissionEntity submission : submissionRepository.findAll()) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaUserController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaUserController.java index 7619473c..5ae15360 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaUserController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaUserController.java @@ -16,7 +16,7 @@ public class JpaUserController { private UserRepository userRepository; Logger logger = LoggerFactory.getLogger(JpaUserController.class); - @GetMapping("/api/users") + @GetMapping("/api/temp/users") public String getUsers() { StringBuilder res = new StringBuilder(); for (UserEntity user : userRepository.findAll()) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java new file mode 100644 index 00000000..e9fe2a20 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java @@ -0,0 +1,36 @@ +package com.ugent.pidgeon.controllers; + +import com.ugent.pidgeon.model.json.CourseWithRelationJson; +import com.ugent.pidgeon.model.json.UserJson; +import com.ugent.pidgeon.postgre.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; +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; + +@RestController +public class UserController { + + @Autowired + private UserRepository userRepository; + + @GetMapping(ApiRoutes.USER_BASE_PATH + "{userid}") + public UserJson getUserById(@PathVariable("userid") Long userid) { + UserJson res = userRepository.findById(userid).map(UserJson::new).orElse(null); + if (res == null) { + return null; + } + List courses = userRepository.findCourseIdsByUserId(userid); + + res.setCourses(courses.stream().map( + c -> new CourseWithRelationJson( + ApiRoutes.COURSE_BASE_PATH + c.getCourseId(), + c.getRelation() + )).toList()); + return res; + } + + +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/CourseWithRelationJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/CourseWithRelationJson.java new file mode 100644 index 00000000..00227d70 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/CourseWithRelationJson.java @@ -0,0 +1,33 @@ +package com.ugent.pidgeon.model.json; + +import com.ugent.pidgeon.postgre.models.types.CourseRelation; + +public class CourseWithRelationJson { + private String url; + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public CourseWithRelationJson() { + } + + public CourseWithRelationJson(String url, CourseRelation relation) { + this.url = url; + this.relation = relation; + } + + private CourseRelation relation; + + public CourseRelation getRelation() { + return relation; + } + + public void setRelation(CourseRelation relation) { + this.relation = relation; + } +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/json/UserJson.java b/backend/app/src/main/java/com/ugent/pidgeon/model/json/UserJson.java new file mode 100644 index 00000000..ba1044e3 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/json/UserJson.java @@ -0,0 +1,106 @@ +package com.ugent.pidgeon.model.json; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.ugent.pidgeon.postgre.models.UserEntity; +import com.ugent.pidgeon.postgre.models.types.UserRole; +import com.ugent.pidgeon.postgre.repository.UserRepository; +import org.codehaus.jackson.annotate.JsonProperty; + +import java.io.Serializable; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +public class UserJson { + + private long id; + private String name; + private String surname; + private String email; + private UserRole role; + + private Timestamp createdAt; + + private String oid; + + private List courses; + + public UserJson() { + } + + public UserJson(UserEntity entity) { + this.id = entity.getId(); + this.name = entity.getName(); + this.surname = entity.getSurname(); + this.email = entity.getEmail(); + this.role = entity.getRole(); + this.createdAt = entity.getCreatedAt(); + this.oid = entity.getMicrosoftToken(); + this.courses = new ArrayList<>(); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getSurname() { + return surname; + } + + public void setSurname(String surname) { + this.surname = surname; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public UserRole getRole() { + return role; + } + + public void setRole(UserRole role) { + this.role = role; + } + + public Timestamp getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(Timestamp createdAt) { + this.createdAt = createdAt; + } + + public String getOid() { + return oid; + } + + public void setOid(String oid) { + this.oid = oid; + } + + + public List getCourses() { + return courses; + } + + public void setCourses(List courses) { + this.courses = courses; + } +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java index bc1b5d86..7a33a4e7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java @@ -18,6 +18,14 @@ public interface CourseWithRelation { CourseRelation getRelation(); } + public interface CourseIdWithRelation { + Long getCourseId(); + CourseRelation getRelation(); + } + + @Query(value = "SELECT c.id as courseId, cu.relation as relation FROM CourseEntity c JOIN CourseUserEntity cu ON c.id = cu.courseId WHERE cu.userId = ?1") + List findCourseIdsByUserId(long id); + /* The 'as' is important here, as it is used to map the result to the CourseWithRelation interface */ @Query(value = "SELECT c as course, cu.relation as relation FROM CourseEntity c JOIN CourseUserEntity cu ON c.id = cu.courseId WHERE cu.userId = ?1") List findCoursesByUserId(long id); From 63ea7434e79b040d6400fec260fc93188d344071 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Mon, 4 Mar 2024 20:29:43 +0100 Subject: [PATCH 02/22] Updated msal config --- .../src/main/resources/application.properties | 16 +++++++++++----- frontend/src/@types/types.d.ts | 12 ++++++++++++ frontend/src/auth/AuthConfig.ts | 6 +++++- frontend/src/index.tsx | 2 ++ frontend/src/util/apiFetch.ts | 3 ++- 5 files changed, 32 insertions(+), 7 deletions(-) create mode 100644 frontend/src/@types/types.d.ts diff --git a/backend/app/src/main/resources/application.properties b/backend/app/src/main/resources/application.properties index b622818b..090df2f2 100644 --- a/backend/app/src/main/resources/application.properties +++ b/backend/app/src/main/resources/application.properties @@ -1,6 +1,10 @@ -#spring.datasource.url = jdbc:postgresql://localhost:5432/postgres -#spring.datasource.username = admin -#spring.datasource.password = root + +# Uncommenting these lines fixes wierd "Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured." error +spring.datasource.url = jdbc:postgresql://localhost:5432/postgres +spring.datasource.username = admin +spring.datasource.password = root + + #spring.jpa.show-sql=true #spring.jpa.database=postgresql # @@ -11,10 +15,12 @@ spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.PostgreSQLDialec # Hibernate ddl auto (create, create-drop, validate, update) spring.jpa.hibernate.ddl-auto = update + server.port=8080 +#testing id: 39136cda-f02f-4305-9b08-45f132bab07e +azure.activedirectory.client-id=83616890-6249-4726-9e0c-87da4dde75b0 -azure.activedirectory.client-id=39136cda-f02f-4305-9b08-45f132bab07e -azure.activedirectory.b2c.client-secret=i1n8Q~57EDI.E2iLxzkW3Q.ixEtVIM4jwN7eDbxK +azure.activedirectory.b2c.client-secret=jE38Q~bU1qLdMKtL05d57nwXHSgrUSXm.vxLNb.n # For UGent auth: d7811cde-ecef-496c-8f91-a1786241b99c # Test auth: 62835335-e5c4-4d22-98f2-9d5b65a06d9d diff --git a/frontend/src/@types/types.d.ts b/frontend/src/@types/types.d.ts new file mode 100644 index 00000000..daec4a4b --- /dev/null +++ b/frontend/src/@types/types.d.ts @@ -0,0 +1,12 @@ + + + + +declare global { + interface Window { + + + } +} + +export {} \ No newline at end of file diff --git a/frontend/src/auth/AuthConfig.ts b/frontend/src/auth/AuthConfig.ts index c5479d52..14127450 100644 --- a/frontend/src/auth/AuthConfig.ts +++ b/frontend/src/auth/AuthConfig.ts @@ -1,9 +1,13 @@ import { Configuration, PopupRequest } from "@azure/msal-browser"; + +const hostedOnServer = window.location.hostname === "sel2-6.ugent.be" // For now, gotta change this when ugent auth is fixed + // Config object to be passed to Msal on creation export const msalConfig: Configuration = { auth: { - clientId: "39136cda-f02f-4305-9b08-45f132bab07e", + // 39136cda-f02f-4305-9b08-45f132bab07e + clientId: hostedOnServer ? "83616890-6249-4726-9e0c-87da4dde75b0" :"39136cda-f02f-4305-9b08-45f132bab07e" , //For UGent auth: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", authority: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", // "https://login.microsoftonline.com/62835335-e5c4-4d22-98f2-9d5b65a06d9d", redirectUri: "/", diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 04d3a091..58fcb1eb 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -15,6 +15,8 @@ msalInstance.initialize().then(() => { msalInstance.setActiveAccount(accounts[0]) } + console.log(accounts); + msalInstance.addEventCallback((event: EventMessage) => { if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) { const payload = event.payload as AuthenticationResult diff --git a/frontend/src/util/apiFetch.ts b/frontend/src/util/apiFetch.ts index d1808623..e9476119 100644 --- a/frontend/src/util/apiFetch.ts +++ b/frontend/src/util/apiFetch.ts @@ -2,6 +2,7 @@ import { ApiRoutes, GET_Responses, POST_Requests, POST_Responses, PUT_Requests } import axios, { AxiosResponse } from "axios"; import {msalInstance} from "../index"; import { AxiosRequestConfig } from "axios"; +import { msalConfig } from "../auth/AuthConfig"; const serverHost ="http://localhost:8080" // window.location.origin; @@ -32,7 +33,7 @@ async function apiFetch(method: "GET" | "POST" | "PUT" | "D if (!accessToken || !tokenExpiry || now >= tokenExpiry) { const response = await msalInstance.acquireTokenSilent({ - scopes: ["39136cda-f02f-4305-9b08-45f132bab07e/.default"], + scopes: [msalConfig.auth.clientId + "/.default"], account: account }); From 5e84cd1cb0382b690ba63d4b673cc92304c619ae Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Mon, 4 Mar 2024 20:30:09 +0100 Subject: [PATCH 03/22] Started making role interceptor --- .../com/ugent/pidgeon/config/AuthConfig.java | 2 +- .../config/JwtAuthenticationFilter.java | 2 +- .../java/com/ugent/pidgeon/config/Roles.java | 14 ++++++ .../pidgeon/config/RolesInterceptor.java | 43 +++++++++++++++++++ .../com/ugent/pidgeon/config/WebConfig.java | 7 +++ .../java/com/ugent/pidgeon/model/User.java | 2 +- 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java b/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java index 280d62b6..2ba50ca7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java @@ -22,7 +22,7 @@ public FilterRegistrationBean filterRegistrationBean() FilterRegistrationBean filter = new FilterRegistrationBean<>(); filter.setFilter(new JwtAuthenticationFilter(tenantId)); - filter.addUrlPatterns("/api/ietswatiknietwiltesten"); + filter.addUrlPatterns("/api/*"); return filter; } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java b/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java index 4ef865e3..ef120468 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java @@ -64,7 +64,7 @@ protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request, 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); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java b/backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java new file mode 100644 index 00000000..6c19c32d --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java @@ -0,0 +1,14 @@ +package com.ugent.pidgeon.config; + +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(); +} \ No newline at end of file diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java new file mode 100644 index 00000000..b2f2199b --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java @@ -0,0 +1,43 @@ +package com.ugent.pidgeon.config; + + +import com.ugent.pidgeon.postgre.models.types.UserRole; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; + +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; + +@Component +public class RolesInterceptor implements HandlerInterceptor { + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if (handler instanceof HandlerMethod) { + HandlerMethod handlerMethod = (HandlerMethod) handler; + Roles rolesAnnotation = handlerMethod.getMethodAnnotation(Roles.class); + if (rolesAnnotation != null) { + List requiredRoles = Arrays.asList(rolesAnnotation.value()); + // Implement your own logic to check if the user has the required role + if (!hasRequiredRole(request, requiredRoles)) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + return false; + } + } + } + return true; + } + + private boolean hasRequiredRole(HttpServletRequest request, List requiredRoles) { + // Example: Check if the user's role matches any of the required roles + // You can access the user's role from the request (e.g., from a session or token) + // Return true if the user has the required role, otherwise return false + return true; + } + + +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java index 738ea1d2..92b408dc 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @@ -15,4 +16,10 @@ public void addCorsMappings(CorsRegistry registry) { .allowedOrigins("*") .allowedHeaders("*"); } + + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(new RolesInterceptor()); + } } \ No newline at end of file diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/User.java b/backend/app/src/main/java/com/ugent/pidgeon/model/User.java index 006341ee..03629b3f 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/User.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/User.java @@ -8,7 +8,7 @@ public class User { public String firstName; public String lastName; public String email; - public List groups; + public List groups; // TODO: remove this public String oid; public User (String name, String firstName, String lastName, String email, List groups, String oid) { From a55bd1de062592e3c715ca4cdd3b39c773e2da80 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Mon, 4 Mar 2024 20:50:03 +0100 Subject: [PATCH 04/22] role test --- .../com/ugent/pidgeon/config/RolesInterceptor.java | 14 ++++++++------ .../pidgeon/controllers/AuthTestController.java | 3 +++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java index b2f2199b..7df634b9 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java @@ -1,14 +1,15 @@ package com.ugent.pidgeon.config; +import com.ugent.pidgeon.model.Auth; import com.ugent.pidgeon.postgre.models.types.UserRole; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +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.lang.reflect.Method; import java.util.Arrays; import java.util.List; @@ -23,7 +24,9 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (rolesAnnotation != null) { List requiredRoles = Arrays.asList(rolesAnnotation.value()); // Implement your own logic to check if the user has the required role - if (!hasRequiredRole(request, requiredRoles)) { + Auth auth = (Auth) SecurityContextHolder.getContext().getAuthentication(); + + if (!hasRequiredRole(auth, requiredRoles)) { response.setStatus(HttpServletResponse.SC_FORBIDDEN); return false; } @@ -32,10 +35,9 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons return true; } - private boolean hasRequiredRole(HttpServletRequest request, List requiredRoles) { - // Example: Check if the user's role matches any of the required roles - // You can access the user's role from the request (e.g., from a session or token) - // Return true if the user has the required role, otherwise return false + private boolean hasRequiredRole(Auth auth, List requiredRoles) { + + System.out.println(" => => " + auth.getUser().email); return true; } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java index d15315b5..a0d7c16a 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java @@ -1,6 +1,8 @@ package com.ugent.pidgeon.controllers; +import com.ugent.pidgeon.config.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; @@ -14,6 +16,7 @@ public class AuthTestController { private UserRepository userRepository; @GetMapping("/api/test") + @Roles({UserRole.student}) public User testApi(HttpServletRequest request, Auth auth) { return auth.getUser(); } From 3b2ee902ca3b6afade98be2c501008e0b44ce9f1 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Mon, 4 Mar 2024 22:37:29 +0100 Subject: [PATCH 05/22] Finished role interceptor --- .../config/JwtAuthenticationFilter.java | 5 ++- .../pidgeon/config/RolesInterceptor.java | 36 ++++++++++++------- .../com/ugent/pidgeon/config/WebConfig.java | 11 +++++- .../controllers/AuthTestController.java | 2 +- .../java/com/ugent/pidgeon/model/User.java | 4 +-- .../pidgeon/postgre/models/UserEntity.java | 12 +++---- .../postgre/repository/UserRepository.java | 3 ++ 7 files changed, 46 insertions(+), 27 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java b/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java index ef120468..dc343abb 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java @@ -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 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); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java index 7df634b9..d476b599 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java @@ -2,9 +2,12 @@ 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; @@ -16,30 +19,37 @@ @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 handlerMethod = (HandlerMethod) handler; + if (handler instanceof HandlerMethod handlerMethod) { Roles rolesAnnotation = handlerMethod.getMethodAnnotation(Roles.class); if (rolesAnnotation != null) { List requiredRoles = Arrays.asList(rolesAnnotation.value()); - // Implement your own logic to check if the user has the required role Auth auth = (Auth) SecurityContextHolder.getContext().getAuthentication(); + System.out.println(auth.getOid()); + UserEntity userEntity = userRepository.findUserByAzureId(auth.getOid()); + + if(userEntity == null) { + // TODO: Create new user object if user does not exist and add to database + response.sendError(HttpServletResponse.SC_FORBIDDEN,"User not found"); + return false; + } - if (!hasRequiredRole(auth, requiredRoles)) { - response.setStatus(HttpServletResponse.SC_FORBIDDEN); + if (!requiredRoles.contains(userEntity.getRole())) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "User does not have required role"); return false; } } } return true; } - - private boolean hasRequiredRole(Auth auth, List requiredRoles) { - - System.out.println(" => => " + auth.getUser().email); - return true; - } - - } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java index 92b408dc..2ddf1058 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java @@ -1,5 +1,6 @@ package com.ugent.pidgeon.config; +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.InterceptorRegistry; @@ -9,6 +10,14 @@ @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("/**") @@ -20,6 +29,6 @@ public void addCorsMappings(CorsRegistry registry) { @Override public void addInterceptors(InterceptorRegistry registry) { - registry.addInterceptor(new RolesInterceptor()); + registry.addInterceptor(rolesInterceptor); } } \ No newline at end of file diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java index 13aec71a..a5070eb3 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java @@ -17,7 +17,7 @@ public class AuthTestController { @GetMapping("/api/test") - @Roles({UserRole.student}) + @Roles({UserRole.student}) // <-- Only students can access this endpoint public User testApi(HttpServletRequest request, Auth auth) { return auth.getUser(); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/User.java b/backend/app/src/main/java/com/ugent/pidgeon/model/User.java index 03629b3f..330c74e7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/User.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/User.java @@ -8,13 +8,11 @@ public class User { public String firstName; public String lastName; public String email; - public List groups; // TODO: remove this public String oid; - public User (String name, String firstName, String lastName, String email, List groups, String oid) { + public User (String name, String firstName, String lastName, String email, String oid) { this.name = name; this.email = email; - this.groups = groups; this.oid = oid; this.firstName = firstName; this.lastName = lastName; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/UserEntity.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/UserEntity.java index 76d629ab..8382fc16 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/UserEntity.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/UserEntity.java @@ -28,18 +28,18 @@ public class UserEntity { @Enumerated(EnumType.STRING) private UserRole role; - @Column(name = "microsoft_token") - private String microsoftToken; + @Column(name = "azure_id") + private String azureId; @Column(name = "created_at") private Timestamp createdAt; - public UserEntity(String name, String surname, String email, UserRole role, String microsoftToken) { + public UserEntity(String name, String surname, String email, UserRole role, String azureId) { this.name = name; this.surname = surname; this.email = email; this.role = role; - this.microsoftToken = microsoftToken; + this.azureId = azureId; } public UserEntity() { @@ -86,11 +86,11 @@ public UserRole getRole() { } public String getMicrosoftToken() { - return microsoftToken; + return azureId; } public void setMicrosoftToken(String microsoftToken) { - this.microsoftToken = microsoftToken; + this.azureId = microsoftToken; } public Timestamp getCreatedAt() { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java index 7a33a4e7..f4a09fb8 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/UserRepository.java @@ -29,4 +29,7 @@ public interface CourseIdWithRelation { /* The 'as' is important here, as it is used to map the result to the CourseWithRelation interface */ @Query(value = "SELECT c as course, cu.relation as relation FROM CourseEntity c JOIN CourseUserEntity cu ON c.id = cu.courseId WHERE cu.userId = ?1") List findCoursesByUserId(long id); + + @Query(value = "SELECT * FROM users WHERE azure_id = ?1", nativeQuery = true) + public UserEntity findUserByAzureId(String id); } From a0099db1d97ef086e7d13f9829e845bc853621b3 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Wed, 6 Mar 2024 14:15:04 +0100 Subject: [PATCH 06/22] Create a new user when new auth is detected --- .../JwtAuthenticationFilter.java | 2 +- .../ugent/pidgeon/{config => auth}/Roles.java | 2 +- .../{config => auth}/RolesInterceptor.java | 9 +++-- .../com/ugent/pidgeon/config/AuthConfig.java | 1 + .../com/ugent/pidgeon/config/WebConfig.java | 2 +- .../controllers/AuthTestController.java | 2 +- frontend/src/auth/AuthConfig.ts | 39 +++++++++---------- 7 files changed, 29 insertions(+), 28 deletions(-) rename backend/app/src/main/java/com/ugent/pidgeon/{config => auth}/JwtAuthenticationFilter.java (99%) rename backend/app/src/main/java/com/ugent/pidgeon/{config => auth}/Roles.java (90%) rename backend/app/src/main/java/com/ugent/pidgeon/{config => auth}/RolesInterceptor.java (82%) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java similarity index 99% rename from backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java rename to backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java index dc343abb..914d73dd 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/JwtAuthenticationFilter.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java @@ -1,4 +1,4 @@ -package com.ugent.pidgeon.config; +package com.ugent.pidgeon.auth; import com.auth0.jwk.Jwk; import com.auth0.jwk.JwkException; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/Roles.java similarity index 90% rename from backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java rename to backend/app/src/main/java/com/ugent/pidgeon/auth/Roles.java index 6c19c32d..6beea441 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/Roles.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/Roles.java @@ -1,4 +1,4 @@ -package com.ugent.pidgeon.config; +package com.ugent.pidgeon.auth; import com.ugent.pidgeon.postgre.models.types.UserRole; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java similarity index 82% rename from backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java rename to backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java index d476b599..b2f413c8 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java @@ -1,4 +1,4 @@ -package com.ugent.pidgeon.config; +package com.ugent.pidgeon.auth; import com.ugent.pidgeon.model.Auth; @@ -39,9 +39,10 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons UserEntity userEntity = userRepository.findUserByAzureId(auth.getOid()); if(userEntity == null) { - // TODO: Create new user object if user does not exist and add to database - response.sendError(HttpServletResponse.SC_FORBIDDEN,"User not found"); - return false; + // TODO: Check if user is a teacher or student (derived from auth object) + 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()); + userRepository.save(userEntity); } if (!requiredRoles.contains(userEntity.getRole())) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java b/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java index 2ba50ca7..3575aea8 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/AuthConfig.java @@ -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; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java index 2ddf1058..0009ac5d 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/config/WebConfig.java @@ -1,10 +1,10 @@ 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.InterceptorRegistry; -import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java index a5070eb3..cd7e27c7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java @@ -1,5 +1,5 @@ package com.ugent.pidgeon.controllers; -import com.ugent.pidgeon.config.Roles; +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; diff --git a/frontend/src/auth/AuthConfig.ts b/frontend/src/auth/AuthConfig.ts index 14127450..581a2327 100644 --- a/frontend/src/auth/AuthConfig.ts +++ b/frontend/src/auth/AuthConfig.ts @@ -1,32 +1,31 @@ -import { Configuration, PopupRequest } from "@azure/msal-browser"; - +import { Configuration, PopupRequest } from "@azure/msal-browser" const hostedOnServer = window.location.hostname === "sel2-6.ugent.be" // For now, gotta change this when ugent auth is fixed // Config object to be passed to Msal on creation export const msalConfig: Configuration = { - auth: { - // 39136cda-f02f-4305-9b08-45f132bab07e - clientId: hostedOnServer ? "83616890-6249-4726-9e0c-87da4dde75b0" :"39136cda-f02f-4305-9b08-45f132bab07e" , - //For UGent auth: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", - authority: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", // "https://login.microsoftonline.com/62835335-e5c4-4d22-98f2-9d5b65a06d9d", - redirectUri: "/", - postLogoutRedirectUri: "/" - }, - system: { - allowNativeBroker: false - }, - cache: { - cacheLocation: "localStorage", - } + auth: { + // 39136cda-f02f-4305-9b08-45f132bab07e + clientId: hostedOnServer ? "83616890-6249-4726-9e0c-87da4dde75b0" : "39136cda-f02f-4305-9b08-45f132bab07e", + //For UGent auth: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", + authority: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", // "https://login.microsoftonline.com/62835335-e5c4-4d22-98f2-9d5b65a06d9d", + redirectUri: "/", + postLogoutRedirectUri: "/", + }, + system: { + allowNativeBroker: false, + }, + cache: { + cacheLocation: "localStorage", + }, } // Add here scopes for id token to be used at MS Identity Platform endpoints. export const loginRequest: PopupRequest = { - scopes: ["User.Read"] -}; + scopes: ["User.Read"], +} // Add here the endpoints for MS Graph API services you would like to use. export const graphConfig = { - graphMeEndpoint: "https://graph.microsoft.com/v1.0/me" -}; \ No newline at end of file + graphMeEndpoint: "https://graph.microsoft.com/v1.0/me", +} From d45ef8ba3ff2cf2d05173e41ef3096a4577d4abb Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:47:52 +0100 Subject: [PATCH 07/22] Created filehandler not yet tested, might have some mistakes --- .../com/ugent/pidgeon/util/Filehandler.java | 65 +++++++++++++++++++ 1 file changed, 65 insertions(+) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java new file mode 100644 index 00000000..bc83213d --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -0,0 +1,65 @@ +package com.ugent.pidgeon.util; + +import org.springframework.web.multipart.MultipartFile; + +import java.io.*; +import java.nio.file.Path; + +public class Filehandler { + + public void saveSubmission(long projectid, long groupid, long submissionid, MultipartFile file) throws IOException { + // Check if the file is empty + if (file.isEmpty()) { + throw new IOException("File is empty"); + } + + try { + // Create a temporary file and save the uploaded file to it + File tempFile = File.createTempFile("uploaded-zip-", ".zip"); + file.transferTo(tempFile); + + // Check if the file is a ZIP file + if (!isZipFile(tempFile)) { + throw new IOException("File is not a ZIP file"); + } + // Create directory + Path directory = getPath(projectid, groupid, submissionid); + File uploadDirectory = new File(directory.toString()); + if (!uploadDirectory.exists()) { + if(!uploadDirectory.mkdir()) { + throw new IOException("Error while creating directory"); + } + } + + // Save the file to the server + Path filePath = directory.resolve("files.zip"); + BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath.toString())); + stream.write(file.getBytes()); + stream.close(); + + } catch (IOException e) { + throw new IOException("Error while saving file" + e.getMessage()); + } + } + + public Path getPath(long projectid, long groupid, long submissionid) { + return Path.of("projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); + } + + private boolean isZipFile(File file) throws IOException { + try (InputStream inputStream = new FileInputStream(file); + BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { + + byte[] signature = new byte[4]; + bufferedInputStream.mark(4); + int bytesRead = bufferedInputStream.read(signature); + + if (bytesRead != 4) { + throw new IOException("Error while reading file"); + } + + // Check if the file signature matches the ZIP format + return (signature[0] == 0x50 && signature[1] == 0x4b && signature[2] == 0x03 && signature[3] == 0x04); + } + } +} From 391103d9a43cb44c9e91ade1f134a2b1a776ff81 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:38:30 +0100 Subject: [PATCH 08/22] Added route to save submission (not completed yet) --- .../FilesubmissiontestController.java | 32 +++++++++++++++++++ .../com/ugent/pidgeon/util/Filehandler.java | 29 +++++++++++++---- .../src/main/resources/application.properties | 4 ++- 3 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java new file mode 100644 index 00000000..8d878f68 --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java @@ -0,0 +1,32 @@ +package com.ugent.pidgeon.controllers; + +import com.ugent.pidgeon.util.Filehandler; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.zip.ZipFile; + +@RestController +public class FilesubmissiontestController { + + @PostMapping("/project/{projectid}") + public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @PathVariable("projectid") long projectid) { + Filehandler filehandler = new Filehandler(); + try { + filehandler.saveSubmission(projectid, 1, 1, file); + return ResponseEntity.ok("File saved"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage()); + } + + } + + @GetMapping("submission/{submissionid}") + public ZipFile getSubmission(@PathVariable("submissionid") long submissionid) { + Filehandler filehandler = new Filehandler(); + return filehandler.getSubmission(submissionid); + } + +} diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index bc83213d..cb2b5623 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -3,7 +3,11 @@ import org.springframework.web.multipart.MultipartFile; import java.io.*; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.StandardCopyOption; +import java.util.logging.Logger; +import java.util.zip.ZipFile; public class Filehandler { @@ -23,26 +27,28 @@ public void saveSubmission(long projectid, long groupid, long submissionid, Mult throw new IOException("File is not a ZIP file"); } // Create directory - Path directory = getPath(projectid, groupid, submissionid); + Path directory = getSubmissionPath(projectid, groupid, submissionid); File uploadDirectory = new File(directory.toString()); if (!uploadDirectory.exists()) { - if(!uploadDirectory.mkdir()) { + Logger.getLogger("Filehandler").info("Creating directory: " + uploadDirectory); + if(!uploadDirectory.mkdirs()) { throw new IOException("Error while creating directory"); } } // Save the file to the server Path filePath = directory.resolve("files.zip"); - BufferedOutputStream stream = new BufferedOutputStream(new FileOutputStream(filePath.toString())); - stream.write(file.getBytes()); - stream.close(); + + try(InputStream stream = new FileInputStream(tempFile)) { + Files.copy(stream, filePath, StandardCopyOption.REPLACE_EXISTING); + } } catch (IOException e) { throw new IOException("Error while saving file" + e.getMessage()); } } - public Path getPath(long projectid, long groupid, long submissionid) { + public Path getSubmissionPath(long projectid, long groupid, long submissionid) { return Path.of("projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } @@ -62,4 +68,15 @@ private boolean isZipFile(File file) throws IOException { return (signature[0] == 0x50 && signature[1] == 0x4b && signature[2] == 0x03 && signature[3] == 0x04); } } + + public ZipFile getSubmission(long submissionid) { + Path directory = getSubmissionPath(1, 1, submissionid); + Path filePath = directory.resolve("files.zip"); + try { + return new ZipFile(filePath.toFile()); + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } } diff --git a/backend/app/src/main/resources/application.properties b/backend/app/src/main/resources/application.properties index b622818b..91309585 100644 --- a/backend/app/src/main/resources/application.properties +++ b/backend/app/src/main/resources/application.properties @@ -25,7 +25,9 @@ azure.activedirectory.tenant-id=d7811cde-ecef-496c-8f91-a1786241b99c #spring.security.oauth2.client.registration.azure.scope=openid, profile, email #spring.security.oauth2.client.registration.azure.provider=azure - +# TODO: this is just temporary, we will need to think of an actual limit at some point +spring.servlet.multipart.max-file-size=10MB +spring.servlet.multipart.max-request-size=10MB #server.ssl.key-store=classpath:keystore.p12 #server.ssl.key-store-password=password From 4af54a6df3ed0f5b70e1d4a9ec2492f7534f134a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:50:53 +0100 Subject: [PATCH 09/22] Added persistent dir --- .gitignore | 1 + .../app/src/main/java/com/ugent/pidgeon/util/Filehandler.java | 4 +++- docker-compose.yaml | 2 ++ 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c2065bc2..fe4c9926 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ out/ ### VS Code ### .vscode/ +backend/app/data/* diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index cb2b5623..bbea0ac8 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -11,6 +11,8 @@ public class Filehandler { + static String BASEPATH = "data"; + public void saveSubmission(long projectid, long groupid, long submissionid, MultipartFile file) throws IOException { // Check if the file is empty if (file.isEmpty()) { @@ -49,7 +51,7 @@ public void saveSubmission(long projectid, long groupid, long submissionid, Mult } public Path getSubmissionPath(long projectid, long groupid, long submissionid) { - return Path.of("projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); + return Path.of(BASEPATH,"projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } private boolean isZipFile(File file) throws IOException { diff --git a/docker-compose.yaml b/docker-compose.yaml index f09124c1..5515884d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -21,6 +21,8 @@ services: - SPRING_DATASOURCE_USERNAME=admin - SPRING_DATASOURCE_PASSWORD=root restart: always + volumes: + - ./backend/app/data:/data:rw db: container_name: db image: 'postgres:latest' From 3c46dafaf8cc445b8e3432313925aeb0ffd0d881 Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Wed, 6 Mar 2024 18:13:51 +0100 Subject: [PATCH 10/22] Finished role filter --- .../main/java/com/ugent/pidgeon/auth/RolesInterceptor.java | 2 +- .../com/ugent/pidgeon/controllers/AuthTestController.java | 2 +- frontend/src/auth/AuthConfig.ts | 3 ++- frontend/src/components/layout/nav/UnauthNav.tsx | 6 +++++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java index b2f413c8..68cd2aa5 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java @@ -45,7 +45,7 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons userRepository.save(userEntity); } - if (!requiredRoles.contains(userEntity.getRole())) { + if (!requiredRoles.contains(userEntity.getRole()) || userEntity.getRole() == UserRole.admin) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "User does not have required role"); return false; } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java index cd7e27c7..830756ea 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/AuthTestController.java @@ -17,7 +17,7 @@ public class AuthTestController { @GetMapping("/api/test") - @Roles({UserRole.student}) // <-- Only students can access this endpoint + @Roles({UserRole.student, UserRole.teacher}) public User testApi(HttpServletRequest request, Auth auth) { return auth.getUser(); } diff --git a/frontend/src/auth/AuthConfig.ts b/frontend/src/auth/AuthConfig.ts index 581a2327..0fa5ff93 100644 --- a/frontend/src/auth/AuthConfig.ts +++ b/frontend/src/auth/AuthConfig.ts @@ -1,6 +1,6 @@ import { Configuration, PopupRequest } from "@azure/msal-browser" -const hostedOnServer = window.location.hostname === "sel2-6.ugent.be" // For now, gotta change this when ugent auth is fixed +const hostedOnServer = false && window.location.hostname === "sel2-6.ugent.be" // For now, gotta change this when ugent auth is fixed // Config object to be passed to Msal on creation export const msalConfig: Configuration = { @@ -11,6 +11,7 @@ export const msalConfig: Configuration = { authority: "https://login.microsoftonline.com/d7811cde-ecef-496c-8f91-a1786241b99c", // "https://login.microsoftonline.com/62835335-e5c4-4d22-98f2-9d5b65a06d9d", redirectUri: "/", postLogoutRedirectUri: "/", + }, system: { allowNativeBroker: false, diff --git a/frontend/src/components/layout/nav/UnauthNav.tsx b/frontend/src/components/layout/nav/UnauthNav.tsx index 84b01434..3d5687b7 100644 --- a/frontend/src/components/layout/nav/UnauthNav.tsx +++ b/frontend/src/components/layout/nav/UnauthNav.tsx @@ -6,7 +6,11 @@ const UnauthNav = () => { const { t } = useTranslation(); const handleLogin = async () => { try { - await msalInstance.loginPopup(); + await msalInstance.loginPopup({ + scopes: ['openid', 'profile', 'User.Read'], + }); + console.log(msalInstance); + } catch (error) { console.error(error) } From 5493611b333c6768ae4f83db9679b175f4a0f81d Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:04:59 +0100 Subject: [PATCH 11/22] Added db logic to saving submission --- .../FilesubmissiontestController.java | 45 ++++++++++++++++--- .../pidgeon/postgre/models/GroupEntity.java | 2 +- .../postgre/models/SubmissionEntity.java | 7 ++- .../postgre/repository/GroupRepository.java | 9 ++++ .../com/ugent/pidgeon/util/Filehandler.java | 13 +++--- 5 files changed, 60 insertions(+), 16 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java index 8d878f68..443f52e6 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java @@ -1,21 +1,56 @@ package com.ugent.pidgeon.controllers; +import com.ugent.pidgeon.postgre.models.FileEntity; +import com.ugent.pidgeon.postgre.models.SubmissionEntity; +import com.ugent.pidgeon.postgre.repository.FileRepository; +import com.ugent.pidgeon.postgre.repository.GroupRepository; +import com.ugent.pidgeon.postgre.repository.SubmissionRepository; import com.ugent.pidgeon.util.Filehandler; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; 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; import java.util.zip.ZipFile; @RestController public class FilesubmissiontestController { - @PostMapping("/project/{projectid}") - public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @PathVariable("projectid") long projectid) { - Filehandler filehandler = new Filehandler(); + @Autowired + private GroupRepository groupRepository; + @Autowired + private FileRepository fileRepository; + @Autowired + private SubmissionRepository submissionRepository; + + @PostMapping("/project/{projectid}") //Route to submit a file, it accepts a multiform with the file and submissionTime + public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid) { + long userId = 1L; //TODO: replace with id of current user + Long groupId = groupRepository.groupIdByProjectAndUser(projectid, userId); + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + + //TODO: executes the tests onces these are implemented try { - filehandler.saveSubmission(projectid, 1, 1, file); + //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()); @@ -26,7 +61,7 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile fil @GetMapping("submission/{submissionid}") public ZipFile getSubmission(@PathVariable("submissionid") long submissionid) { Filehandler filehandler = new Filehandler(); - return filehandler.getSubmission(submissionid); + return Filehandler.getSubmission(submissionid); } } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/GroupEntity.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/GroupEntity.java index 725aeea3..8de35cca 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/GroupEntity.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/GroupEntity.java @@ -7,7 +7,7 @@ public class GroupEntity { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="group_id", nullable=false) private long id; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/SubmissionEntity.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/SubmissionEntity.java index c43ba695..0be6a243 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/SubmissionEntity.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/SubmissionEntity.java @@ -1,15 +1,14 @@ package com.ugent.pidgeon.postgre.models; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; +import jakarta.persistence.*; + import java.sql.Timestamp; @Entity @Table(name="submissions") public class SubmissionEntity { @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name="submission_id", nullable=false) private long id; diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java index 95c3cfb2..83b930ef 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java @@ -20,4 +20,13 @@ public interface GroupRepository extends JpaRepository{ JOIN GroupEntity g ON g.clusterId = gc.id WHERE g.id = ?1""") List findProjectsByGroupId(long id); + + + @Query(value = """ + SELECT g.id FROM GroupEntity g + JOIN GroupUserEntity gu ON g.id = gu.groupId + JOIN GroupClusterEntity gc ON g.clusterId = gc.id + JOIN ProjectEntity p ON p.groupClusterId = gc.id + WHERE p.id = ?1 AND gu.userId = ?2""") + Long groupIdByProjectAndUser(long projectId, long userId); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index bbea0ac8..533f21d1 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -12,8 +12,9 @@ public class Filehandler { static String BASEPATH = "data"; + static String SUBMISSION_FILENAME = "files.zip"; - public void saveSubmission(long projectid, long groupid, long submissionid, MultipartFile file) throws IOException { + public static String saveSubmission(Path directory, MultipartFile file) throws IOException { // Check if the file is empty if (file.isEmpty()) { throw new IOException("File is empty"); @@ -29,7 +30,6 @@ public void saveSubmission(long projectid, long groupid, long submissionid, Mult throw new IOException("File is not a ZIP file"); } // Create directory - Path directory = getSubmissionPath(projectid, groupid, submissionid); File uploadDirectory = new File(directory.toString()); if (!uploadDirectory.exists()) { Logger.getLogger("Filehandler").info("Creating directory: " + uploadDirectory); @@ -39,22 +39,23 @@ public void saveSubmission(long projectid, long groupid, long submissionid, Mult } // Save the file to the server - Path filePath = directory.resolve("files.zip"); + Path filePath = directory.resolve(SUBMISSION_FILENAME); try(InputStream stream = new FileInputStream(tempFile)) { Files.copy(stream, filePath, StandardCopyOption.REPLACE_EXISTING); } + return filePath.getFileName().toString(); } catch (IOException e) { throw new IOException("Error while saving file" + e.getMessage()); } } - public Path getSubmissionPath(long projectid, long groupid, long submissionid) { + static public Path getSubmissionPath(long projectid, long groupid, long submissionid) { return Path.of(BASEPATH,"projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } - private boolean isZipFile(File file) throws IOException { + static boolean isZipFile(File file) throws IOException { try (InputStream inputStream = new FileInputStream(file); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { @@ -71,7 +72,7 @@ private boolean isZipFile(File file) throws IOException { } } - public ZipFile getSubmission(long submissionid) { + public static ZipFile getSubmission(long submissionid) { Path directory = getSubmissionPath(1, 1, submissionid); Path filePath = directory.resolve("files.zip"); try { From 8398c6e21846815978ebc67bed12e2416f78c6ef Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:17:31 +0100 Subject: [PATCH 12/22] Added route to fetch submission --- .../FilesubmissiontestController.java | 48 ++++++++++++++++--- .../postgre/repository/GroupRepository.java | 8 ++++ .../com/ugent/pidgeon/util/Filehandler.java | 14 ++---- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java index 443f52e6..f15b9598 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java @@ -7,13 +7,19 @@ 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.InputStreamResource; +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.io.FileInputStream; import java.nio.file.Path; import java.sql.Timestamp; +import java.util.logging.Logger; import java.util.zip.ZipFile; @RestController @@ -26,11 +32,10 @@ public class FilesubmissiontestController { @Autowired private SubmissionRepository submissionRepository; - @PostMapping("/project/{projectid}") //Route to submit a file, it accepts a multiform with the file and submissionTime + @PostMapping("/project/{projectid}/submit") //Route to submit a file, it accepts a multiform with the file and submissionTime public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid) { long userId = 1L; //TODO: replace with id of current user Long groupId = groupRepository.groupIdByProjectAndUser(projectid, userId); - Timestamp timestamp = new Timestamp(System.currentTimeMillis()); //TODO: executes the tests onces these are implemented try { @@ -58,10 +63,39 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile fil } - @GetMapping("submission/{submissionid}") - public ZipFile getSubmission(@PathVariable("submissionid") long submissionid) { - Filehandler filehandler = new Filehandler(); - return Filehandler.getSubmission(submissionid); - } + @GetMapping("submissions/{submissionid}") + public ResponseEntity getSubmission(@PathVariable("submissionid") long submissionid) { + long userId = 1L; //TODO: replace with id of current user + // 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); + } + } } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java index 83b930ef..78b94660 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/GroupRepository.java @@ -13,6 +13,14 @@ public interface GroupRepository extends JpaRepository{ @Query(value= "SELECT u FROM UserEntity u JOIN GroupUserEntity gu ON u.id = gu.userId WHERE gu.groupId = ?1") List findCourseUsersByGroupId(long id); + @Query(value = """ + SELECT CASE WHEN EXISTS ( + SELECT gu + FROM GroupUserEntity gu + WHERE gu.userId = ?2 and gu.groupId = ?1 + ) THEN true ELSE false END""") + Boolean userInGroup(long groupId, long userId); + @Query(value = """ SELECT p.id FROM ProjectEntity p diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index 533f21d1..8017b691 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -1,6 +1,8 @@ package com.ugent.pidgeon.util; +import org.springframework.core.io.InputStreamResource; import org.springframework.web.multipart.MultipartFile; +import org.springframework.core.io.Resource; import java.io.*; import java.nio.file.Files; @@ -32,7 +34,6 @@ public static String saveSubmission(Path directory, MultipartFile file) throws I // Create directory File uploadDirectory = new File(directory.toString()); if (!uploadDirectory.exists()) { - Logger.getLogger("Filehandler").info("Creating directory: " + uploadDirectory); if(!uploadDirectory.mkdirs()) { throw new IOException("Error while creating directory"); } @@ -72,14 +73,7 @@ static boolean isZipFile(File file) throws IOException { } } - public static ZipFile getSubmission(long submissionid) { - Path directory = getSubmissionPath(1, 1, submissionid); - Path filePath = directory.resolve("files.zip"); - try { - return new ZipFile(filePath.toFile()); - } catch (IOException e) { - e.printStackTrace(); - return null; - } + public static Resource getSubmissionAsResource(Path path) throws IOException { + return new InputStreamResource(new FileInputStream(path.toFile())); } } From 871ce4a9d2462bb0bf07aa14cc67b93e5c9f3e7a Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 21:58:27 +0100 Subject: [PATCH 13/22] use Tika to check for zip file --- backend/app/build.gradle | 2 ++ .../com/ugent/pidgeon/util/Filehandler.java | 23 ++++++++----------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/backend/app/build.gradle b/backend/app/build.gradle index a7302389..2710f535 100644 --- a/backend/app/build.gradle +++ b/backend/app/build.gradle @@ -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' diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index 8017b691..a2f52045 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -1,5 +1,6 @@ package com.ugent.pidgeon.util; +import org.apache.tika.Tika; import org.springframework.core.io.InputStreamResource; import org.springframework.web.multipart.MultipartFile; import org.springframework.core.io.Resource; @@ -48,7 +49,7 @@ public static String saveSubmission(Path directory, MultipartFile file) throws I return filePath.getFileName().toString(); } catch (IOException e) { - throw new IOException("Error while saving file" + e.getMessage()); + throw new IOException(e.getMessage()); } } @@ -56,21 +57,17 @@ static public Path getSubmissionPath(long projectid, long groupid, long submissi return Path.of(BASEPATH,"projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } - static boolean isZipFile(File file) throws IOException { - try (InputStream inputStream = new FileInputStream(file); - BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream)) { + public static boolean isZipFile(File file) throws IOException { + // Create a Tika instance + Tika tika = new Tika(); - byte[] signature = new byte[4]; - bufferedInputStream.mark(4); - int bytesRead = bufferedInputStream.read(signature); + // Detect the file type + String fileType = tika.detect(file); - if (bytesRead != 4) { - throw new IOException("Error while reading file"); - } + // Check if the detected file type is a ZIP file + Logger.getGlobal().info("File type: " + fileType); + return fileType.equals("application/zip") || fileType.equals("application/x-zip-compressed"); - // Check if the file signature matches the ZIP format - return (signature[0] == 0x50 && signature[1] == 0x4b && signature[2] == 0x03 && signature[3] == 0x04); - } } public static Resource getSubmissionAsResource(Path path) throws IOException { From 7131c809fa13831a1ba5720bbd8477c9ef596f54 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Wed, 6 Mar 2024 22:23:24 +0100 Subject: [PATCH 14/22] submit:check to see if user is part of the project --- .../controllers/FilesubmissiontestController.java | 6 ++++++ .../postgre/repository/ProjectRepository.java | 12 ++++++++++++ 2 files changed, 18 insertions(+) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java index f15b9598..0b7c396b 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java @@ -4,6 +4,7 @@ import com.ugent.pidgeon.postgre.models.SubmissionEntity; 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; @@ -31,12 +32,17 @@ public class FilesubmissiontestController { 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 public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid) { long userId = 1L; //TODO: replace with id of current user 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 diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/ProjectRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/ProjectRepository.java index 7cacecb3..afe0675b 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/ProjectRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/ProjectRepository.java @@ -2,9 +2,21 @@ import com.ugent.pidgeon.postgre.models.ProjectEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import java.util.List; public interface ProjectRepository extends JpaRepository { List findByCourseId(long courseId); + + @Query(value = """ + SELECT CASE WHEN EXISTS ( + SELECT gu + FROM GroupUserEntity gu + INNER JOIN GroupEntity g ON gu.groupId = g.id + INNER JOIN GroupClusterEntity gc ON g.clusterId = gc.id + INNER JOIN ProjectEntity p ON p.groupClusterId = gc.id + WHERE gu.userId = ?1 and p.id = ?2 + ) THEN true ELSE false END""") + Boolean userPartOfProject(long projectId, long userId); } From 80aec246b957bd71839142e7b845b3a9b4788867 Mon Sep 17 00:00:00 2001 From: Arne Dierick Date: Thu, 7 Mar 2024 17:07:19 +0100 Subject: [PATCH 15/22] start of branch, not done yet --- .../controllers/JpaProjectTestController.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java new file mode 100644 index 00000000..9b7b7aed --- /dev/null +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java @@ -0,0 +1,78 @@ +package com.ugent.pidgeon.controllers; + +import com.ugent.pidgeon.postgre.models.FileEntity; +import com.ugent.pidgeon.postgre.models.ProjectEntity; +import com.ugent.pidgeon.postgre.models.TestEntity; +import com.ugent.pidgeon.postgre.repository.FileRepository; +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.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.List; + +@RestController +public class JpaProjectTestController { + + @Autowired + private ProjectRepository projectRepository; + @Autowired + private FileRepository fileRepository; + @Autowired + private TestRepository testRepository; + + @PutMapping("/project/{projectid}/tests") + public ResponseEntity updateTests( + @RequestParam("dockerfile") MultipartFile dockerfile, + @RequestParam("structurefile") MultipartFile structurefile, + @PathVariable("projectid") long projectId) { + +// // Delete existing test files linked to the current project +// deleteExistingTestFiles(projectId); +// +// // Save new test files +// FileEntity dockerFileEntity = saveFile(dockerfile); +// FileEntity structureFileEntity = saveFile(structurefile); +// +// // Create a new test entity +// TestEntity testEntity = new TestEntity(); +// testEntity.setDockerFile(dockerFileEntity); +// testEntity.setStructureFile(structureFileEntity); +// testRepository.save(testEntity); +// +// // Update project-test relationship +// ProjectEntity projectEntity = projectRepository.findById(projectId) +// .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); +// projectEntity.setTests(List.of(testEntity)); +// projectRepository.save(projectEntity); + + return ResponseEntity.ok("Tests updated successfully."); + } + +// private void deleteExistingTestFiles(long projectId) { +// // Retrieve the project entity +// ProjectEntity projectEntity = projectRepository.findById(projectId) +// .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); +// +// // Delete existing test files linked to the project +// testRepository.deleteAll(existingTests); +// } +// +// private FileEntity saveFile(MultipartFile multipartFile) throws IOException { +// // Save the file to the database and return the corresponding file entity +// // Implement this method according to your file storage mechanism +// // For simplicity, let's assume the file is saved directly to the database +// FileEntity fileEntity = new FileEntity(); +// fileEntity.setFileName(multipartFile.getOriginalFilename()); +// fileEntity.setFileData(multipartFile.getBytes()); +// return fileRepository.save(fileEntity); +// } +} + From 2b0c01a1533ea8a323a462acfcb7a9506a4c8d7b Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Thu, 7 Mar 2024 17:10:48 +0100 Subject: [PATCH 16/22] Added role dectorator de existing routes --- .../pidgeon/auth/JwtAuthenticationFilter.java | 4 ++-- .../ugent/pidgeon/auth/RolesInterceptor.java | 3 +-- .../FilesubmissiontestController.java | 18 ++++++++++-------- .../pidgeon/controllers/UserController.java | 4 ++++ .../java/com/ugent/pidgeon/model/Auth.java | 19 +++++++++++++++---- 5 files changed, 32 insertions(+), 16 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java index 914d73dd..7f7c1581 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/JwtAuthenticationFilter.java @@ -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()); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java index 68cd2aa5..f36ea1fe 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java @@ -35,15 +35,14 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons if (rolesAnnotation != null) { List requiredRoles = Arrays.asList(rolesAnnotation.value()); Auth auth = (Auth) SecurityContextHolder.getContext().getAuthentication(); - System.out.println(auth.getOid()); UserEntity userEntity = userRepository.findUserByAzureId(auth.getOid()); if(userEntity == null) { - // TODO: Check if user is a teacher or student (derived from auth object) 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()); 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"); diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java index 0b7c396b..ef089f83 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/FilesubmissiontestController.java @@ -1,14 +1,16 @@ 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.InputStreamResource; import org.springframework.core.io.Resource; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -17,11 +19,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; -import java.io.FileInputStream; import java.nio.file.Path; import java.sql.Timestamp; -import java.util.logging.Logger; -import java.util.zip.ZipFile; @RestController public class FilesubmissiontestController { @@ -36,8 +35,9 @@ public class FilesubmissiontestController { private ProjectRepository projectRepository; @PostMapping("/project/{projectid}/submit") //Route to submit a file, it accepts a multiform with the file and submissionTime - public ResponseEntity submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid) { - long userId = 1L; //TODO: replace with id of current user + @Roles({UserRole.teacher, UserRole.student}) + public ResponseEntity 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)) { @@ -70,13 +70,15 @@ public ResponseEntity submitFile(@RequestParam("file") MultipartFile fil } @GetMapping("submissions/{submissionid}") - public ResponseEntity getSubmission(@PathVariable("submissionid") long submissionid) { - long userId = 1L; //TODO: replace with id of current user + @Roles({UserRole.teacher, UserRole.student}) + public ResponseEntity 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); } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java index e9fe2a20..4c4fb123 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/UserController.java @@ -1,7 +1,10 @@ package com.ugent.pidgeon.controllers; +import com.ugent.pidgeon.auth.Roles; +import com.ugent.pidgeon.model.Auth; import com.ugent.pidgeon.model.json.CourseWithRelationJson; import com.ugent.pidgeon.model.json.UserJson; +import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.postgre.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -17,6 +20,7 @@ public class UserController { private UserRepository userRepository; @GetMapping(ApiRoutes.USER_BASE_PATH + "{userid}") + @Roles({UserRole.student, UserRole.teacher}) public UserJson getUserById(@PathVariable("userid") Long userid) { UserJson res = userRepository.findById(userid).map(UserJson::new).orElse(null); if (res == null) { diff --git a/backend/app/src/main/java/com/ugent/pidgeon/model/Auth.java b/backend/app/src/main/java/com/ugent/pidgeon/model/Auth.java index 492ebe41..4add2b68 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/model/Auth.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/model/Auth.java @@ -1,6 +1,8 @@ package com.ugent.pidgeon.model; +import com.ugent.pidgeon.postgre.models.UserEntity; import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.util.Assert; @@ -10,6 +12,7 @@ public class Auth extends AbstractAuthenticationToken { private static final long serialVersionUID = 620L; private final User user; + private UserEntity userEntity; @@ -31,12 +34,20 @@ public String getOid(){ return user.oid; } - public static org.springframework.security.authentication.UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) { - return new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(principal, credentials); + public void setUserEntity(UserEntity user){ + userEntity = user; } - public static org.springframework.security.authentication.UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials, Collection authorities) { - return new org.springframework.security.authentication.UsernamePasswordAuthenticationToken(principal, credentials, authorities); + public UserEntity getUserEntity(){ + return userEntity; + } + + public static UsernamePasswordAuthenticationToken unauthenticated(Object principal, Object credentials) { + return new UsernamePasswordAuthenticationToken(principal, credentials); + } + + public static UsernamePasswordAuthenticationToken authenticated(Object principal, Object credentials, Collection authorities) { + return new UsernamePasswordAuthenticationToken(principal, credentials, authorities); } public Object getCredentials() { From 8d4c190cf01ac7ce5890ca91ce25e262d240a5eb Mon Sep 17 00:00:00 2001 From: usserwoutV2 Date: Thu, 7 Mar 2024 17:27:12 +0100 Subject: [PATCH 17/22] Added timestamp when user is created --- .../src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java index f36ea1fe..a0e687cb 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/auth/RolesInterceptor.java @@ -13,6 +13,7 @@ import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; +import java.sql.Timestamp; import java.util.Arrays; import java.util.List; @@ -40,6 +41,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons 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); From ce1b297a9e3f25af6cd674008efeb1af2f53b4a1 Mon Sep 17 00:00:00 2001 From: Arne Dierick Date: Thu, 7 Mar 2024 21:43:50 +0100 Subject: [PATCH 18/22] added putmapping for project tests --- .../controllers/JpaProjectTestController.java | 95 +++++++++++-------- .../pidgeon/postgre/models/TestEntity.java | 34 ++++--- 2 files changed, 79 insertions(+), 50 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java index 9b7b7aed..862e819c 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java @@ -14,9 +14,13 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; -import java.io.IOException; +import java.nio.file.Paths; import java.util.List; +import java.util.Objects; @RestController public class JpaProjectTestController { @@ -30,49 +34,62 @@ public class JpaProjectTestController { @PutMapping("/project/{projectid}/tests") public ResponseEntity updateTests( - @RequestParam("dockerfile") MultipartFile dockerfile, - @RequestParam("structurefile") MultipartFile structurefile, + @RequestParam("dockerimage") MultipartFile dockerImage, + @RequestParam("dockertest") MultipartFile dockerTest, + @RequestParam("structuretest") MultipartFile structureTest, @PathVariable("projectid") long projectId) { + ProjectEntity projectEntity = projectRepository.findById(projectId) + .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); -// // Delete existing test files linked to the current project -// deleteExistingTestFiles(projectId); -// -// // Save new test files -// FileEntity dockerFileEntity = saveFile(dockerfile); -// FileEntity structureFileEntity = saveFile(structurefile); -// -// // Create a new test entity -// TestEntity testEntity = new TestEntity(); -// testEntity.setDockerFile(dockerFileEntity); -// testEntity.setStructureFile(structureFileEntity); -// testRepository.save(testEntity); -// -// // Update project-test relationship -// ProjectEntity projectEntity = projectRepository.findById(projectId) -// .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); -// projectEntity.setTests(List.of(testEntity)); -// projectRepository.save(projectEntity); + long userId = 1L; //TODO: replace with id of current user + if(!projectRepository.userPartOfProject(projectId, userId)){ + return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You aren't part of this project"); + } + + // Assuming the project entity contains a reference to the associated test entity + Long testId = projectEntity.getTestId(); + + // Update the test entity with the new files + TestEntity testEntity = testRepository.findById(testId) + .orElseThrow(() -> new IllegalArgumentException("Test not found with id: " + testId)); + try { + // Update the test entity with the new files and upload them to the server. + testEntity.setDockerImage(saveTest(dockerImage, projectId, userId)); + testEntity.setDockerTest(saveTest(dockerTest, projectId, userId)); + testEntity.setStructureTestId(saveTest(structureTest, projectId, userId)); + + // Save the updated test entity + testRepository.save(testEntity); + + } catch (Exception e){ + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage()); + } return ResponseEntity.ok("Tests updated successfully."); } -// private void deleteExistingTestFiles(long projectId) { -// // Retrieve the project entity -// ProjectEntity projectEntity = projectRepository.findById(projectId) -// .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); -// -// // Delete existing test files linked to the project -// testRepository.deleteAll(existingTests); -// } -// -// private FileEntity saveFile(MultipartFile multipartFile) throws IOException { -// // Save the file to the database and return the corresponding file entity -// // Implement this method according to your file storage mechanism -// // For simplicity, let's assume the file is saved directly to the database -// FileEntity fileEntity = new FileEntity(); -// fileEntity.setFileName(multipartFile.getOriginalFilename()); -// fileEntity.setFileData(multipartFile.getBytes()); -// return fileRepository.save(fileEntity); -// } + + // Hulpfunctie om de testen over te zetten naar de server en de database op de correcte plaats + private long saveTest(MultipartFile file, long projectId, long userId) throws IOException { + // Check if the file is empty + if (file.isEmpty()) { + throw new IOException("File is empty"); + } + + // Create directory if it doesn't exist + Path projectDirectory = Paths.get("/data/projects/" + projectId + "/tests/"); + if (!Files.exists(projectDirectory)) { + Files.createDirectories(projectDirectory); + } + + // Save the file to the server + Path filePath = projectDirectory.resolve(Objects.requireNonNull(file.getOriginalFilename())); + Files.write(filePath, file.getBytes()); + + // Save the file entity to the database and return its ID + FileEntity fileEntity = new FileEntity(file.getOriginalFilename(), filePath.toString(), userId); + FileEntity savedFileEntity = fileRepository.save(fileEntity); + return savedFileEntity.getId(); + } } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java index a39cfb67..085e947f 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java @@ -13,17 +13,21 @@ public class TestEntity { private long id; @Column(name = "docker_image") - private String dockerImage; + private long dockerImage; - @Column(name = "file_test_id") - private long fileTestId; + @Column(name = "docker_test") + private long dockerTest; + + @Column(name = "structure_test_id") + private long structureTestId; public TestEntity() { } - public TestEntity(String dockerImage, long fileTestId) { + public TestEntity(long dockerImage, long dockerTest, long structureTestId) { this.dockerImage = dockerImage; - this.fileTestId = fileTestId; + this.dockerTest = dockerTest; + this.structureTestId = structureTestId; } @@ -35,19 +39,27 @@ public Long getId() { return id; } - public String getDockerImage() { + public long getDockerImage() { return dockerImage; } - public void setDockerImage(String dockerImage) { + public void setDockerImage(long dockerImage) { this.dockerImage = dockerImage; } - public long getFileTestId() { - return fileTestId; + public long getDockerTest() { + return dockerTest; + } + + public void setDockerTest(long dockerTest) { + this.dockerTest = dockerTest; + } + + public long getStructureTestId() { + return structureTestId; } - public void setFileTestId(long fileTestId) { - this.fileTestId = fileTestId; + public void setStructureTestId(long structureTestId) { + this.structureTestId = structureTestId; } } From d521620095d5cc3d7cf2ff90983690bd9a1cb266 Mon Sep 17 00:00:00 2001 From: Arne Dierick Date: Fri, 8 Mar 2024 13:06:47 +0100 Subject: [PATCH 19/22] code is opgekuist, pull request kan uitgevoerd worden mits goedkeuring --- .../controllers/JpaProjectTestController.java | 71 +++++++------------ .../com/ugent/pidgeon/util/Filehandler.java | 24 +++++++ 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java index 862e819c..951df958 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java @@ -1,8 +1,11 @@ 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.types.UserRole; +import com.ugent.pidgeon.util.Filehandler; import com.ugent.pidgeon.postgre.models.ProjectEntity; -import com.ugent.pidgeon.postgre.models.TestEntity; import com.ugent.pidgeon.postgre.repository.FileRepository; import com.ugent.pidgeon.postgre.repository.ProjectRepository; import com.ugent.pidgeon.postgre.repository.TestRepository; @@ -33,63 +36,43 @@ public class JpaProjectTestController { private TestRepository testRepository; @PutMapping("/project/{projectid}/tests") + @Roles({UserRole.teacher}) public ResponseEntity updateTests( @RequestParam("dockerimage") MultipartFile dockerImage, @RequestParam("dockertest") MultipartFile dockerTest, @RequestParam("structuretest") MultipartFile structureTest, - @PathVariable("projectid") long projectId) { + @PathVariable("projectid") long projectId, + Auth auth) { + ProjectEntity projectEntity = projectRepository.findById(projectId) .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); - long userId = 1L; //TODO: replace with id of current user + long userId = auth.getUserEntity().getId(); if(!projectRepository.userPartOfProject(projectId, userId)){ return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You aren't part of this project"); } - - // Assuming the project entity contains a reference to the associated test entity - Long testId = projectEntity.getTestId(); - - // Update the test entity with the new files - TestEntity testEntity = testRepository.findById(testId) - .orElseThrow(() -> new IllegalArgumentException("Test not found with id: " + testId)); try { - // Update the test entity with the new files and upload them to the server. - testEntity.setDockerImage(saveTest(dockerImage, projectId, userId)); - testEntity.setDockerTest(saveTest(dockerTest, projectId, userId)); - testEntity.setStructureTestId(saveTest(structureTest, projectId, userId)); - - // Save the updated test entity - testRepository.save(testEntity); - - } catch (Exception e){ - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving file: " + e.getMessage()); + // Save the files + Path dockerImagePath = Filehandler.saveTest(dockerImage, projectId); + Path dockerTestPath = Filehandler.saveTest(dockerTest, projectId); + Path structureTestPath = Filehandler.saveTest(structureTest, projectId); + + saveFileEntity(dockerImagePath, projectId, userId); + saveFileEntity(dockerTestPath, projectId, userId); + saveFileEntity(structureTestPath, projectId, userId); + + return ResponseEntity.ok("Tests updated successfully."); + } catch (IOException e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error while saving files: " + e.getMessage()); } - - return ResponseEntity.ok("Tests updated successfully."); } - - // Hulpfunctie om de testen over te zetten naar de server en de database op de correcte plaats - private long saveTest(MultipartFile file, long projectId, long userId) throws IOException { - // Check if the file is empty - if (file.isEmpty()) { - throw new IOException("File is empty"); - } - - // Create directory if it doesn't exist - Path projectDirectory = Paths.get("/data/projects/" + projectId + "/tests/"); - if (!Files.exists(projectDirectory)) { - Files.createDirectories(projectDirectory); - } - - // Save the file to the server - Path filePath = projectDirectory.resolve(Objects.requireNonNull(file.getOriginalFilename())); - Files.write(filePath, file.getBytes()); - - // Save the file entity to the database and return its ID - FileEntity fileEntity = new FileEntity(file.getOriginalFilename(), filePath.toString(), userId); - FileEntity savedFileEntity = fileRepository.save(fileEntity); - return savedFileEntity.getId(); + // Hulpfunctie om de tests correct op de database te zetten + private void saveFileEntity(Path filePath, long projectId, long userId) throws IOException { + // Save the file entity to the database + FileEntity fileEntity = new FileEntity(filePath.getFileName().toString(), filePath.toString(), userId); + fileRepository.save(fileEntity); } + } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index a2f52045..8b20db3a 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -1,5 +1,7 @@ package com.ugent.pidgeon.util; +import com.ugent.pidgeon.postgre.models.FileEntity; +import com.ugent.pidgeon.postgre.repository.FileRepository; import org.apache.tika.Tika; import org.springframework.core.io.InputStreamResource; import org.springframework.web.multipart.MultipartFile; @@ -8,7 +10,9 @@ import java.io.*; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.Objects; import java.util.logging.Logger; import java.util.zip.ZipFile; @@ -73,4 +77,24 @@ public static boolean isZipFile(File file) throws IOException { public static Resource getSubmissionAsResource(Path path) throws IOException { return new InputStreamResource(new FileInputStream(path.toFile())); } + + // Hulpfunctie om de testbestanden over te zetten naar de server + public static Path saveTest(MultipartFile file, long projectId) throws IOException { + // Check if the file is empty + if (file.isEmpty()) { + throw new IOException("File is empty"); + } + + // Create directory if it doesn't exist + Path projectDirectory = Paths.get("/data/projects/" + projectId + "/tests/"); + if (!Files.exists(projectDirectory)) { + Files.createDirectories(projectDirectory); + } + + // Save the file to the server + Path filePath = projectDirectory.resolve(Objects.requireNonNull(file.getOriginalFilename())); + Files.write(filePath, file.getBytes()); + + return filePath; + } } From 7659d16d8e943eec983ea0b473046b1aaf4c5982 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 8 Mar 2024 18:14:40 +0100 Subject: [PATCH 20/22] method for testpath --- .../src/main/java/com/ugent/pidgeon/util/Filehandler.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java index 8b20db3a..1ae67c08 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/util/Filehandler.java @@ -61,6 +61,10 @@ static public Path getSubmissionPath(long projectid, long groupid, long submissi return Path.of(BASEPATH,"projects", String.valueOf(projectid), String.valueOf(groupid), String.valueOf(submissionid)); } + static public Path getTestPath(long projectid) { + return Path.of(BASEPATH,"projects", String.valueOf(projectid), "tests"); + } + public static boolean isZipFile(File file) throws IOException { // Create a Tika instance Tika tika = new Tika(); @@ -86,7 +90,7 @@ public static Path saveTest(MultipartFile file, long projectId) throws IOExcepti } // Create directory if it doesn't exist - Path projectDirectory = Paths.get("/data/projects/" + projectId + "/tests/"); + Path projectDirectory = getTestPath(projectId); if (!Files.exists(projectDirectory)) { Files.createDirectories(projectDirectory); } From 3cd6a4a7a578f1c1336251affe21001bb9dc2ff9 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:21:38 +0100 Subject: [PATCH 21/22] updated dockerimage to string +fix db link --- .../controllers/JpaProjectTestController.java | 39 ++++++++++++++----- .../pidgeon/postgre/models/TestEntity.java | 8 ++-- .../postgre/repository/TestRepository.java | 6 +++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java index 951df958..647a39a7 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java @@ -3,6 +3,7 @@ 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.TestEntity; import com.ugent.pidgeon.postgre.models.types.UserRole; import com.ugent.pidgeon.util.Filehandler; import com.ugent.pidgeon.postgre.models.ProjectEntity; @@ -24,6 +25,7 @@ import java.nio.file.Paths; import java.util.List; import java.util.Objects; +import java.util.Optional; @RestController public class JpaProjectTestController { @@ -36,9 +38,9 @@ public class JpaProjectTestController { private TestRepository testRepository; @PutMapping("/project/{projectid}/tests") - @Roles({UserRole.teacher}) + //@Roles({UserRole.teacher}) public ResponseEntity updateTests( - @RequestParam("dockerimage") MultipartFile dockerImage, + @RequestParam("dockerimage") String dockerImage, @RequestParam("dockertest") MultipartFile dockerTest, @RequestParam("structuretest") MultipartFile structureTest, @PathVariable("projectid") long projectId, @@ -47,19 +49,36 @@ public ResponseEntity updateTests( ProjectEntity projectEntity = projectRepository.findById(projectId) .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); - long userId = auth.getUserEntity().getId(); + long userId = 1;//auth.getUserEntity().getId(); if(!projectRepository.userPartOfProject(projectId, userId)){ return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You aren't part of this project"); } try { - // Save the files - Path dockerImagePath = Filehandler.saveTest(dockerImage, projectId); + // Save the files on server Path dockerTestPath = Filehandler.saveTest(dockerTest, projectId); Path structureTestPath = Filehandler.saveTest(structureTest, projectId); - saveFileEntity(dockerImagePath, projectId, userId); - saveFileEntity(dockerTestPath, projectId, userId); - saveFileEntity(structureTestPath, projectId, userId); + // Save file entities to the database + FileEntity dockertestFileEntity = saveFileEntity(dockerTestPath, projectId, userId); + FileEntity structuretestFileEntity = saveFileEntity(structureTestPath, projectId, userId); + + // Create/update test entity + Optional testEntity = testRepository.findByProjectId(projectId); + if (testEntity.isEmpty()) { + TestEntity newTestEntity = new TestEntity(dockerImage, dockertestFileEntity.getId(), structuretestFileEntity.getId()); + + newTestEntity = testRepository.save(newTestEntity); + projectEntity.setTestId(newTestEntity.getId()); + // Update project entity because first time test is created so id is not set + projectRepository.save(projectEntity); + } else { + TestEntity newTestEntity = testEntity.get(); + newTestEntity.setDockerImage(dockerImage); + newTestEntity.setDockerTest(dockertestFileEntity.getId()); + newTestEntity.setStructureTestId(structuretestFileEntity.getId()); + testRepository.save(newTestEntity); + } + return ResponseEntity.ok("Tests updated successfully."); } catch (IOException e) { @@ -68,10 +87,10 @@ public ResponseEntity updateTests( } // Hulpfunctie om de tests correct op de database te zetten - private void saveFileEntity(Path filePath, long projectId, long userId) throws IOException { + private FileEntity saveFileEntity(Path filePath, long projectId, long userId) throws IOException { // Save the file entity to the database FileEntity fileEntity = new FileEntity(filePath.getFileName().toString(), filePath.toString(), userId); - fileRepository.save(fileEntity); + return fileRepository.save(fileEntity); } } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java index 085e947f..13c0b126 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/models/TestEntity.java @@ -13,7 +13,7 @@ public class TestEntity { private long id; @Column(name = "docker_image") - private long dockerImage; + private String dockerImage; @Column(name = "docker_test") private long dockerTest; @@ -24,7 +24,7 @@ public class TestEntity { public TestEntity() { } - public TestEntity(long dockerImage, long dockerTest, long structureTestId) { + public TestEntity(String dockerImage, long dockerTest, long structureTestId) { this.dockerImage = dockerImage; this.dockerTest = dockerTest; this.structureTestId = structureTestId; @@ -39,11 +39,11 @@ public Long getId() { return id; } - public long getDockerImage() { + public String getDockerImage() { return dockerImage; } - public void setDockerImage(long dockerImage) { + public void setDockerImage(String dockerImage) { this.dockerImage = dockerImage; } diff --git a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/TestRepository.java b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/TestRepository.java index cfa9cd32..7a0c5fb6 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/TestRepository.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/postgre/repository/TestRepository.java @@ -2,6 +2,12 @@ import com.ugent.pidgeon.postgre.models.TestEntity; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.Optional; public interface TestRepository extends JpaRepository { + + @Query(value ="SELECT t FROM ProjectEntity p JOIN TestEntity t ON p.testId = t.id WHERE p.id = ?1") + Optional findByProjectId(long projectId); } From 5e1fdcd2457647ebe0503884de2972403aa9a342 Mon Sep 17 00:00:00 2001 From: Aqua-sc <108478185+Aqua-sc@users.noreply.github.com> Date: Fri, 8 Mar 2024 19:24:30 +0100 Subject: [PATCH 22/22] re-enabled auth --- .../ugent/pidgeon/controllers/JpaProjectTestController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java index 647a39a7..dcadb083 100644 --- a/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java +++ b/backend/app/src/main/java/com/ugent/pidgeon/controllers/JpaProjectTestController.java @@ -38,7 +38,7 @@ public class JpaProjectTestController { private TestRepository testRepository; @PutMapping("/project/{projectid}/tests") - //@Roles({UserRole.teacher}) + @Roles({UserRole.teacher}) public ResponseEntity updateTests( @RequestParam("dockerimage") String dockerImage, @RequestParam("dockertest") MultipartFile dockerTest, @@ -49,7 +49,7 @@ public ResponseEntity updateTests( ProjectEntity projectEntity = projectRepository.findById(projectId) .orElseThrow(() -> new IllegalArgumentException("Project not found with id: " + projectId)); - long userId = 1;//auth.getUserEntity().getId(); + long userId = auth.getUserEntity().getId(); if(!projectRepository.userPartOfProject(projectId, userId)){ return ResponseEntity.status(HttpStatus.FORBIDDEN).body("You aren't part of this project"); }