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); + } }