Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Feature/role interceptor #47

Merged
merged 11 commits into from
Mar 7, 2024
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.ugent.pidgeon.config;
package com.ugent.pidgeon.auth;

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

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

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

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


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

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

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

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

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


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

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

@Component
public class RolesInterceptor implements HandlerInterceptor {


private final UserRepository userRepository;

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


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

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

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

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

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

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

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

@Configuration
public class WebConfig implements WebMvcConfigurer {


private final RolesInterceptor rolesInterceptor;

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

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


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

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


@GetMapping("/api/test")
@Roles({UserRole.student, UserRole.teacher})
public User testApi(HttpServletRequest request, Auth auth) {
return auth.getUser();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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 {
Expand All @@ -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<String> 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<String> submitFile(@RequestParam("file") MultipartFile file, @RequestParam("submissionTime") Timestamp time, @PathVariable("projectid") long projectid,Auth auth) {
long userId = auth.getUserEntity().getId();
Long groupId = groupRepository.groupIdByProjectAndUser(projectid, userId);

if (!projectRepository.userPartOfProject(projectid, userId)) {
Expand Down Expand Up @@ -70,13 +70,15 @@ public ResponseEntity<String> submitFile(@RequestParam("file") MultipartFile fil
}

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

if (!groupRepository.userInGroup(submission.getGroupId(), userId)) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(null);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JpaCourseController {
@Autowired
private ProjectRepository projectRepository;

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

@GetMapping("/api/groups")
@GetMapping("/api/temp/groups")
public List<String> getGroups() {
List<String> res = new ArrayList<>();
for (GroupEntity group : groupRepository.findAll()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class JpaProjectController {
@Autowired
private TestRepository testRepository;

@GetMapping("/api/projects")
@GetMapping("/api/temp/projects")
public List<String> getProjects() {
List<String> res = new ArrayList<>();
for (ProjectEntity project : projectRepository.findAll()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public class JpaSubmissionController {
@Autowired
private FileRepository fileRepository;

@GetMapping("/api/submissions")
@GetMapping("/api/temp/submissions")
public List<String> getSubmissions() {
List<String> res = new ArrayList<>();
for (SubmissionEntity submission : submissionRepository.findAll()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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;
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}")
@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) {
return null;
}
List<UserRepository.CourseIdWithRelation> courses = userRepository.findCourseIdsByUserId(userid);

res.setCourses(courses.stream().map(
c -> new CourseWithRelationJson(
ApiRoutes.COURSE_BASE_PATH + c.getCourseId(),
c.getRelation()
)).toList());
return res;
}


}
19 changes: 15 additions & 4 deletions backend/app/src/main/java/com/ugent/pidgeon/model/Auth.java
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -10,6 +12,7 @@ public class Auth extends AbstractAuthenticationToken {
private static final long serialVersionUID = 620L;

private final User user;
private UserEntity userEntity;



Expand All @@ -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<? extends GrantedAuthority> 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<? extends GrantedAuthority> authorities) {
return new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
}

public Object getCredentials() {
Expand Down
4 changes: 1 addition & 3 deletions backend/app/src/main/java/com/ugent/pidgeon/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,11 @@ public class User {
public String firstName;
public String lastName;
public String email;
public List<String> groups;
public String oid;

public User (String name, String firstName, String lastName, String email, List<String> 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;
Expand Down
Loading
Loading