From 424813aaaeb28fadebf0bafacdf3b2b0153878eb Mon Sep 17 00:00:00 2001 From: Yash Raj <56453897+yesyash@users.noreply.github.com> Date: Sat, 13 Jul 2024 18:11:40 +0530 Subject: [PATCH] create `AuthorizedRoles` annotation and `AuthorizedRolesAspect` (#136) * create a model to store user_skill and add remove unused columns in users and skills model * create a model to store user_skill and add/remove unused columns in users and skills model * change id to integer * check if a skill already exists before creating one, make `updated_at` optional in TrackedProperties * remove unit and integration test and remove skill service & skill service implementation * add todo * rename SkillType to SkillTypeEnum * set logging level debug in application-dev instead of application * add todo * remove endorsements list * add reference to user table in skills modal * Build api to create a new endorsement * create api to update an endorsement * remove unused files * remove unused code in endorsement controller * chagne import order * change the skills project structure to match the new one * rename exceptions folder to small case exceptions and create user not found and skill already exists exceptions * create enums folder and move skill type enum to the folder * rename Conifg to config, move generic response and jwtAuthenticationFilter to utils * add api to get all endorsements for a skill using skill id in skillsapi * remove skills package * fix build error * move api to create a endorsement to the new folder structure * add api to update endorsement in apis/endorsements api and remove old Endorsement folder * move health check api to the api folder & metric service inside the services folder * fix formatting * create annotation and aspect to handle authorized roles to an api * add authorizred roles annotation to skillsapi & endorsementsapi class * set authorized role for creating a skill to only superuser * fix formatting --- .../annotations/AuthorizedRoles.java | 13 ++++ .../RDS/skilltree/apis/EndorsementsApi.java | 3 + .../com/RDS/skilltree/apis/SkillsApi.java | 4 ++ .../aspects/AuthorizedRolesAspect.java | 60 +++++++++++++++++++ .../exceptions/ForbiddenException.java | 7 +++ .../exceptions/GlobalExceptionHandler.java | 6 ++ 6 files changed, 93 insertions(+) create mode 100644 skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java create mode 100644 skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java create mode 100644 skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java diff --git a/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java b/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java new file mode 100644 index 00000000..32e798b8 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/annotations/AuthorizedRoles.java @@ -0,0 +1,13 @@ +package com.RDS.skilltree.annotations; + +import com.RDS.skilltree.User.UserRoleEnum; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ElementType.METHOD, ElementType.TYPE}) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizedRoles { + UserRoleEnum[] value() default {}; +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java index d775d5c7..c418a5dd 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/EndorsementsApi.java @@ -1,5 +1,7 @@ package com.RDS.skilltree.apis; +import com.RDS.skilltree.User.UserRoleEnum; +import com.RDS.skilltree.annotations.AuthorizedRoles; import com.RDS.skilltree.services.EndorsementService; import com.RDS.skilltree.viewmodels.CreateEndorsementViewModel; import com.RDS.skilltree.viewmodels.EndorsementViewModel; @@ -15,6 +17,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("v1/endorsements") +@AuthorizedRoles({UserRoleEnum.USER, UserRoleEnum.SUPERUSER}) public class EndorsementsApi { private final EndorsementService endorsementService; diff --git a/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java index ebf07ed6..b5e76f43 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/apis/SkillsApi.java @@ -1,5 +1,7 @@ package com.RDS.skilltree.apis; +import com.RDS.skilltree.User.UserRoleEnum; +import com.RDS.skilltree.annotations.AuthorizedRoles; import com.RDS.skilltree.services.EndorsementService; import com.RDS.skilltree.services.SkillService; import com.RDS.skilltree.viewmodels.CreateSkillViewModel; @@ -19,6 +21,7 @@ @RestController @RequiredArgsConstructor @RequestMapping("v1/skills") +@AuthorizedRoles({UserRoleEnum.USER, UserRoleEnum.SUPERUSER}) public class SkillsApi { private final SkillService skillService; private final EndorsementService endorsementService; @@ -29,6 +32,7 @@ public ResponseEntity> getAll() { } @PostMapping + @AuthorizedRoles({UserRoleEnum.SUPERUSER}) public ResponseEntity create(@Valid @RequestBody CreateSkillViewModel skill) { return ResponseEntity.ok(skillService.create(skill)); } diff --git a/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java b/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java new file mode 100644 index 00000000..65ff6c2f --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/aspects/AuthorizedRolesAspect.java @@ -0,0 +1,60 @@ +package com.RDS.skilltree.aspects; + +import com.RDS.skilltree.User.JwtUserModel; +import com.RDS.skilltree.User.UserRoleEnum; +import com.RDS.skilltree.annotations.AuthorizedRoles; +import com.RDS.skilltree.exceptions.ForbiddenException; +import java.lang.reflect.Method; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; + +@Aspect +@Component +public class AuthorizedRolesAspect { + + @Around("@within(authorizedRoles) || @annotation(authorizedRoles)") + public Object authorize(ProceedingJoinPoint joinPoint, AuthorizedRoles authorizedRoles) + throws Throwable { + JwtUserModel jwtDetails = + (JwtUserModel) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); + UserRoleEnum role = jwtDetails.getRole(); + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + Method method = signature.getMethod(); + Class targetClass = method.getDeclaringClass(); + + AuthorizedRoles methodAuthorized = method.getAnnotation(AuthorizedRoles.class); + AuthorizedRoles classAuthorized = targetClass.getAnnotation(AuthorizedRoles.class); + + UserRoleEnum[] allowedRoles = {}; + + if (methodAuthorized != null) { + allowedRoles = methodAuthorized.value(); + } else if (classAuthorized != null) { + allowedRoles = classAuthorized.value(); + } else { + // If no roles are specified, proceed with the method execution + joinPoint.proceed(); + } + + if (!isAuthorized(role, allowedRoles)) { + throw new ForbiddenException("You're not authorized to make this request"); + } + + return joinPoint.proceed(); + } + + private boolean isAuthorized(UserRoleEnum userRole, UserRoleEnum[] allowedRoles) { + for (UserRoleEnum role : allowedRoles) { + if (role.equals(userRole)) { + return true; + } + } + + return false; + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java new file mode 100644 index 00000000..47b3c385 --- /dev/null +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/ForbiddenException.java @@ -0,0 +1,7 @@ +package com.RDS.skilltree.exceptions; + +public class ForbiddenException extends RuntimeException { + public ForbiddenException(String message) { + super(message); + } +} diff --git a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java index 50df4784..c21e447f 100644 --- a/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java +++ b/skill-tree/src/main/java/com/RDS/skilltree/exceptions/GlobalExceptionHandler.java @@ -129,4 +129,10 @@ public ResponseEntity handleEndorsementNotException( log.error("Exception - Error : {}", ex.getMessage(), ex); return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.NOT_FOUND); } + + @ExceptionHandler(ForbiddenException.class) + public ResponseEntity handleForbiddenException(ForbiddenException ex, WebRequest request) { + log.error("Exception - Error : {}", ex.getMessage(), ex); + return new ResponseEntity<>(new GenericResponse<>(null, ex.getMessage()), HttpStatus.FORBIDDEN); + } }