From 431a7595d48a7bc81e0003fb94e623fe87650885 Mon Sep 17 00:00:00 2001 From: Sabah u Din Irfan Date: Fri, 3 May 2024 17:03:41 +0100 Subject: [PATCH] PO-327: Add custom annotation for authorisation (#342) * PO-327: Add custom annotation for authorisation * PO-327: Add custom annotation for authorisation * Fix typos --- .../aspect/AuthorizationAspect.java | 45 ++++- .../aspect/AuthorizationAspectService.java | 38 ++++ .../aspect/AuthorizedRoleHasPermission.java | 44 +++++ .../aspect/PermissionNotAllowedException.java | 9 + .../aspect/RoleNotFoundException.java | 8 + .../DefendantAccountController.java | 3 - .../service/legacy/LegacyNoteService.java | 4 + .../hmcts/opal/service/opal/NoteService.java | 4 + .../opal/service/proxy/NoteServiceProxy.java | 4 + .../AuthorizationAspectServiceTest.java | 154 ++++++++++++--- .../aspect/AuthorizationAspectTest.java | 176 ++++++++++++------ 11 files changed, 400 insertions(+), 89 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizedRoleHasPermission.java create mode 100644 src/main/java/uk/gov/hmcts/opal/authorisation/aspect/RoleNotFoundException.java diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspect.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspect.java index 31bae02c8..a72407582 100644 --- a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspect.java +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspect.java @@ -6,11 +6,15 @@ import org.aspectj.lang.annotation.Aspect; import org.springframework.stereotype.Component; import uk.gov.hmcts.opal.authentication.exception.MissingRequestHeaderException; +import uk.gov.hmcts.opal.authorisation.model.Role; import uk.gov.hmcts.opal.authorisation.model.UserState; import uk.gov.hmcts.opal.service.opal.UserStateService; +import java.util.function.Supplier; + import static uk.gov.hmcts.opal.authorisation.aspect.AuthorizationAspectService.AUTHORIZATION; import static uk.gov.hmcts.opal.util.PermissionUtil.checkAnyRoleHasPermission; +import static uk.gov.hmcts.opal.util.PermissionUtil.checkRoleHasPermission; @Aspect @Component @@ -26,14 +30,47 @@ public Object checkAuthorization(ProceedingJoinPoint joinPoint, ) throws Throwable { Object[] args = joinPoint.getArgs(); - String authHeaderValue = authorizationAspectService.getRequestHeaderValue(args); - String bearerToken = authorizationAspectService.getAuthorization(authHeaderValue) - .orElseThrow(() -> new MissingRequestHeaderException(AUTHORIZATION)); - UserState userState = userStateService.getUserStateUsingAuthToken(bearerToken); + UserState userState = getUserState(args); if (checkAnyRoleHasPermission(userState, authorizedAnyRoleHasPermission.value())) { return joinPoint.proceed(); } throw new PermissionNotAllowedException(authorizedAnyRoleHasPermission.value()); } + + @Around("execution(* *(*)) && @annotation(authorizedRoleHasPermission)") + public Object checkAuthorization(ProceedingJoinPoint joinPoint, + AuthorizedRoleHasPermission authorizedRoleHasPermission + ) throws Throwable { + + Object[] args = joinPoint.getArgs(); + UserState userState = getUserState(args); + + Role role = authorizationAspectService.getRole(args, userState); + if (checkRoleHasPermission(role, authorizedRoleHasPermission.value())) { + return joinPoint.proceed(); + } + throw new PermissionNotAllowedException(authorizedRoleHasPermission.value(), role); + } + + /** + * This will infer the UserState from the arguments of the annotated method if present. + * If no argument of USerState then it will fetch based on the bearer token of the current user. + * + * @param args arguments of the annotated method + * @return UserState object for the user + */ + private UserState getUserState(Object[] args) { + return authorizationAspectService.getUserState(args) + .orElseGet(getUserStateSupplier(args)); + } + + private Supplier getUserStateSupplier(Object[] args) { + return () -> { + String authHeaderValue = authorizationAspectService.getRequestHeaderValue(args); + String bearerToken = authorizationAspectService.getAuthorization(authHeaderValue) + .orElseThrow(() -> new MissingRequestHeaderException(AUTHORIZATION)); + return userStateService.getUserStateUsingAuthToken(bearerToken); + }; + } } diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectService.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectService.java index e7388fbe4..daf3c7132 100644 --- a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectService.java +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectService.java @@ -1,11 +1,21 @@ package uk.gov.hmcts.opal.authorisation.aspect; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import uk.gov.hmcts.opal.authorisation.model.Role; +import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.dto.AddNoteDto; +import uk.gov.hmcts.opal.dto.NoteDto; +import java.util.Arrays; import java.util.Optional; +import static java.lang.String.format; +import static uk.gov.hmcts.opal.util.PermissionUtil.getRequiredRole; + +@Slf4j @Component public class AuthorizationAspectService { @@ -31,4 +41,32 @@ public Optional getAuthorization(String authHeaderValue) { } return Optional.empty(); } + + public Role getRole(Object[] args, UserState userState) { + for (Object arg : args) { + if (arg instanceof Role) { + return (Role) arg; + } else if (arg instanceof AddNoteDto addNoteDto) { + return getRequiredRole(userState, addNoteDto.getBusinessUnitId()); + } else if (arg instanceof NoteDto noteDto) { + return getRequiredRole(userState, noteDto.getBusinessUnitId()); + } + } + throw new RoleNotFoundException(format( + "Can't infer the role for user %s. " + + "Annotated method needs to have arguments of types (Role, AddNoteDto, NoteDto).", + userState.getUserName() + )); + } + + public Optional getUserState(Object[] args) { + return getArgument(args, UserState.class); + } + + public Optional getArgument(Object[] args, Class clazz) { + return Arrays.stream(args) + .filter(clazz::isInstance) + .map(clazz::cast) + .findFirst(); + } } diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizedRoleHasPermission.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizedRoleHasPermission.java new file mode 100644 index 000000000..011c290aa --- /dev/null +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizedRoleHasPermission.java @@ -0,0 +1,44 @@ +package uk.gov.hmcts.opal.authorisation.aspect; + +import uk.gov.hmcts.opal.authorisation.model.Permissions; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * The AuthorizedRoleHasPermission annotation is used to authorise or deny execution of a business method + * based on the role. + * If the given role has the permission then only execution will be allowed, otherwise PermissionNotAllowedException + * will be thrown. + * For example: + * The role can be one of the argument of the annotated method. + *
+ *      @AuthorizedRoleHasPermission(Permissions.ACCOUNT_ENQUIRY)
+ *      public void businessMethod(Role role) { ... }
+ * 
+ * The role can be inferred if one of the argument is of type NoteDto, the role will be picked by matching + * businessUnitId of NoteDto argument within the userState roles. + * If this role has the permission then only execution will be allowed, otherwise PermissionNotAllowedException + * will be thrown. + * For example: + *
+ *      @AuthorizedRoleHasPermission(Permissions.ACCOUNT_ENQUIRY_NOTES)
+ *      public NoteDto saveNote(NoteDto noteDto) { .. }
+ *  
+ * The role can be inferred if one of the argument is of type NoteDto, the role will be picked by matching + * businessUnitId of AddNoteDto argument within the userState roles. + * If this role has the permission then only execution will be allowed, otherwise PermissionNotAllowedException + * will be thrown. + * For example: + *
+ *      @AuthorizedRoleHasPermission(Permissions.ACCOUNT_ENQUIRY_NOTES)
+ *      public NoteDto saveNote(AddNoteDto addNoteDto) { .. }
+ * 
+ */ +@Target(ElementType.METHOD) +@Retention(RetentionPolicy.RUNTIME) +public @interface AuthorizedRoleHasPermission { + Permissions value(); +} diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java index 984315a8b..d7dd1430f 100644 --- a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/PermissionNotAllowedException.java @@ -2,14 +2,23 @@ import lombok.Getter; import uk.gov.hmcts.opal.authorisation.model.Permissions; +import uk.gov.hmcts.opal.authorisation.model.Role; @Getter public class PermissionNotAllowedException extends RuntimeException { private final Permissions permission; + private final Role role; public PermissionNotAllowedException(Permissions value) { super(value + " permission is not allowed for the user"); this.permission = value; + this.role = null; + } + + public PermissionNotAllowedException(Permissions permission, Role role) { + super(permission + " permission is not allowed for the role " + role); + this.permission = permission; + this.role = role; } } diff --git a/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/RoleNotFoundException.java b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/RoleNotFoundException.java new file mode 100644 index 000000000..d3a8543d8 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/opal/authorisation/aspect/RoleNotFoundException.java @@ -0,0 +1,8 @@ +package uk.gov.hmcts.opal.authorisation.aspect; + +public class RoleNotFoundException extends RuntimeException { + + public RoleNotFoundException(String message) { + super(message); + } +} diff --git a/src/main/java/uk/gov/hmcts/opal/controllers/DefendantAccountController.java b/src/main/java/uk/gov/hmcts/opal/controllers/DefendantAccountController.java index e8dff5ab3..f7737c7ac 100644 --- a/src/main/java/uk/gov/hmcts/opal/controllers/DefendantAccountController.java +++ b/src/main/java/uk/gov/hmcts/opal/controllers/DefendantAccountController.java @@ -15,7 +15,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import uk.gov.hmcts.opal.authorisation.model.Permissions; import uk.gov.hmcts.opal.authorisation.model.Role; import uk.gov.hmcts.opal.authorisation.model.UserState; import uk.gov.hmcts.opal.dto.AccountDetailsDto; @@ -35,7 +34,6 @@ import static uk.gov.hmcts.opal.util.HttpUtil.buildCreatedResponse; import static uk.gov.hmcts.opal.util.HttpUtil.buildResponse; -import static uk.gov.hmcts.opal.util.PermissionUtil.checkRoleHasPermission; import static uk.gov.hmcts.opal.util.PermissionUtil.getRequiredRole; @RestController @@ -120,7 +118,6 @@ public ResponseEntity addNote( UserState userState = userStateService.getUserStateUsingAuthToken(authHeaderValue); Role role = getRequiredRole(userState, addNote.getBusinessUnitId()); - checkRoleHasPermission(role, Permissions.ACCOUNT_ENQUIRY_NOTES); NoteDto noteDto = NoteDto.builder() .associatedRecordId(addNote.getAssociatedRecordId()) diff --git a/src/main/java/uk/gov/hmcts/opal/service/legacy/LegacyNoteService.java b/src/main/java/uk/gov/hmcts/opal/service/legacy/LegacyNoteService.java index 22b9b6608..7835f7dc9 100644 --- a/src/main/java/uk/gov/hmcts/opal/service/legacy/LegacyNoteService.java +++ b/src/main/java/uk/gov/hmcts/opal/service/legacy/LegacyNoteService.java @@ -4,6 +4,7 @@ import org.slf4j.Logger; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; +import uk.gov.hmcts.opal.authorisation.aspect.AuthorizedRoleHasPermission; import uk.gov.hmcts.opal.config.properties.LegacyGatewayProperties; import uk.gov.hmcts.opal.dto.NoteDto; import uk.gov.hmcts.opal.dto.legacy.LegacySaveNoteRequestDto; @@ -13,6 +14,8 @@ import java.util.List; +import static uk.gov.hmcts.opal.authorisation.model.Permissions.ACCOUNT_ENQUIRY_NOTES; + @Service @Slf4j(topic = "LegacyNoteService") public class LegacyNoteService extends LegacyService implements NoteServiceInterface { @@ -30,6 +33,7 @@ protected Logger getLog() { } @Override + @AuthorizedRoleHasPermission(ACCOUNT_ENQUIRY_NOTES) public NoteDto saveNote(NoteDto noteDto) { log.info("Saving Note: {}", noteDto); diff --git a/src/main/java/uk/gov/hmcts/opal/service/opal/NoteService.java b/src/main/java/uk/gov/hmcts/opal/service/opal/NoteService.java index 14710847c..ee77d276c 100644 --- a/src/main/java/uk/gov/hmcts/opal/service/opal/NoteService.java +++ b/src/main/java/uk/gov/hmcts/opal/service/opal/NoteService.java @@ -9,6 +9,7 @@ import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import uk.gov.hmcts.opal.authorisation.aspect.AuthorizedAnyRoleHasPermission; +import uk.gov.hmcts.opal.authorisation.aspect.AuthorizedRoleHasPermission; import uk.gov.hmcts.opal.authorisation.model.Permissions; import uk.gov.hmcts.opal.dto.NoteDto; import uk.gov.hmcts.opal.dto.search.NoteSearchDto; @@ -24,6 +25,8 @@ import java.util.Optional; import java.util.stream.Collectors; +import static uk.gov.hmcts.opal.authorisation.model.Permissions.ACCOUNT_ENQUIRY_NOTES; + @Service @RequiredArgsConstructor @Slf4j(topic = "NoteService") @@ -35,6 +38,7 @@ public class NoteService implements NoteServiceInterface { @Override @FeatureToggle(feature = "add-note", value = true) + @AuthorizedRoleHasPermission(ACCOUNT_ENQUIRY_NOTES) public NoteDto saveNote(NoteDto noteDto) { // Restrict the 'postedBy' to 20 characters length String postedBy = Optional.ofNullable(noteDto.getPostedBy()) diff --git a/src/main/java/uk/gov/hmcts/opal/service/proxy/NoteServiceProxy.java b/src/main/java/uk/gov/hmcts/opal/service/proxy/NoteServiceProxy.java index 872cc8be9..24998cc2d 100644 --- a/src/main/java/uk/gov/hmcts/opal/service/proxy/NoteServiceProxy.java +++ b/src/main/java/uk/gov/hmcts/opal/service/proxy/NoteServiceProxy.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Service; +import uk.gov.hmcts.opal.authorisation.aspect.AuthorizedRoleHasPermission; import uk.gov.hmcts.opal.dto.NoteDto; import uk.gov.hmcts.opal.dto.search.NoteSearchDto; import uk.gov.hmcts.opal.launchdarkly.FeatureToggle; @@ -13,6 +14,8 @@ import java.util.List; +import static uk.gov.hmcts.opal.authorisation.model.Permissions.ACCOUNT_ENQUIRY_NOTES; + @Service @RequiredArgsConstructor @Qualifier("noteServiceProxy") @@ -28,6 +31,7 @@ private NoteServiceInterface getCurrentModeService() { @Override @FeatureToggle(feature = "add-note", value = true) + @AuthorizedRoleHasPermission(ACCOUNT_ENQUIRY_NOTES) public NoteDto saveNote(NoteDto noteDto) { return getCurrentModeService().saveNote(noteDto); } diff --git a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectServiceTest.java b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectServiceTest.java index eb1515a01..b809a8313 100644 --- a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectServiceTest.java +++ b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectServiceTest.java @@ -1,6 +1,7 @@ package uk.gov.hmcts.opal.authorisation.aspect; import jakarta.servlet.http.HttpServletRequest; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -9,57 +10,166 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; +import uk.gov.hmcts.opal.authorisation.model.Permission; +import uk.gov.hmcts.opal.authorisation.model.Role; +import uk.gov.hmcts.opal.authorisation.model.UserState; +import uk.gov.hmcts.opal.dto.AddNoteDto; import java.util.Optional; +import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.when; @SpringBootTest(classes = AuthorizationAspectService.class) @ExtendWith(MockitoExtension.class) class AuthorizationAspectServiceTest { + static final Role ROLE = Role.builder() + .businessUnitId((short) 12) + .businessUserId("BU123") + .permissions(Set.of( + Permission.builder() + .permissionId(1L) + .permissionName("Notes") + .build())) + .build(); + + static final UserState USER_STATE = UserState.builder() + .userId(123L).userName("John Smith") + .roles(Set.of(ROLE)) + .build(); + @MockBean private HttpServletRequest servletRequest; @Autowired private AuthorizationAspectService authorizationAspectService; - @Test - void getRequestHeaderValue_WhenNoStringArgumentExists_ReturnsNull() { - Object[] args = {123, true, new Object()}; + @Nested + class GetRequestHeaderValue { + @Test + void getRequestHeaderValue_WhenNoStringArgumentExists_ReturnsNull() { + Object[] args = {123, true, new Object()}; - String headerValue = authorizationAspectService.getRequestHeaderValue(args); + String headerValue = authorizationAspectService.getRequestHeaderValue(args); - assertEquals(null, headerValue); + assertNull(headerValue); + } } - @Test - void getAuthorization_WhenAuthHeaderValueNotNull_ReturnsOptionalWithValue() { - String authHeaderValue = "Bearer token"; + @Nested + class GetAuthorization { + @Test + void getAuthorization_WhenAuthHeaderValueNotNull_ReturnsOptionalWithValue() { + String authHeaderValue = "Bearer token"; + + Optional result = authorizationAspectService.getAuthorization(authHeaderValue); + + assertEquals(Optional.of(authHeaderValue), result); + } + + @Test + void getAuthorization_WhenRequestAttributesNotNull_ReturnsOptionalWithValue() { + String authHeaderValue = "Bearer token"; + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); + when(servletRequest.getHeader(AuthorizationAspectService.AUTHORIZATION)).thenReturn(authHeaderValue); + + Optional result = authorizationAspectService.getAuthorization(null); + + assertEquals(Optional.of(authHeaderValue), result); + } - Optional result = authorizationAspectService.getAuthorization(authHeaderValue); + @Test + void getAuthorization_WhenRequestAttributesNull_ReturnsOptionalEmpty() { + RequestContextHolder.setRequestAttributes(null); - assertEquals(Optional.of(authHeaderValue), result); + Optional result = authorizationAspectService.getAuthorization(null); + + assertEquals(Optional.empty(), result); + } } - @Test - void getAuthorization_WhenRequestAttributesNotNull_ReturnsOptionalWithValue() { - String authHeaderValue = "Bearer token"; - RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(servletRequest)); - when(servletRequest.getHeader(AuthorizationAspectService.AUTHORIZATION)).thenReturn(authHeaderValue); + @Nested + class GetRole { + @Test + void getRole_WhenInvalidArguments() { + Object[] args = {"invalid"}; + String expectedMessage = "Can't infer the role for user John Smith." + + " Annotated method needs to have arguments of types (Role, AddNoteDto, NoteDto)."; + + RoleNotFoundException exception = assertThrows( + RoleNotFoundException.class, + () -> authorizationAspectService.getRole(args, USER_STATE) + ); + + assertEquals(expectedMessage, exception.getMessage()); + } + + @Test + void getRole_WhenAddNoteDtoArgument() { + AddNoteDto addNoteDto = AddNoteDto.builder().businessUnitId((short) 12).build(); + Object[] args = {addNoteDto}; + + Role actualRole = authorizationAspectService.getRole(args, USER_STATE); - Optional result = authorizationAspectService.getAuthorization(null); + assertEquals(ROLE, actualRole); + } - assertEquals(Optional.of(authHeaderValue), result); + @Test + void getRole_WhenRoleArgument() { + Role expectedRole = ROLE; + Object[] args = {expectedRole}; + + Role actualRole = authorizationAspectService.getRole(args, USER_STATE); + + assertEquals(expectedRole, actualRole); + } } - @Test - void getAuthorization_WhenRequestAttributesNull_ReturnsOptionalEmpty() { - RequestContextHolder.setRequestAttributes(null); + @Nested + class GetArgument { + @Test + void testGetArgumentWithInvalidArgument() { + Object[] args = {"someString", 42}; + + Optional result = authorizationAspectService.getArgument(args, UserState.class); + + assertTrue(result.isEmpty()); + } + + @Test + void testGetArgumentWithEmptyArgs() { + Object[] args = {}; + + Optional result = authorizationAspectService.getArgument(args, UserState.class); + + assertTrue(result.isEmpty()); + } + + @Test + void testGetStringWithValidArgument() { + String str = "testString"; + Object[] args = {USER_STATE, str, 42}; + + Optional result = authorizationAspectService.getArgument(args, String.class); + + assertTrue(result.isPresent()); + assertEquals(str, result.get()); + } + + @Test + void testGetUserStateWithValidArgument() { + String str = "testString"; + Object[] args = {USER_STATE, str, 42}; - Optional result = authorizationAspectService.getAuthorization(null); + Optional result = authorizationAspectService.getArgument(args, UserState.class); - assertEquals(Optional.empty(), result); + assertTrue(result.isPresent()); + assertEquals(USER_STATE, result.get()); + } } } diff --git a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectTest.java b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectTest.java index e7c766e47..44ef8acc1 100644 --- a/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectTest.java +++ b/src/test/java/uk/gov/hmcts/opal/authorisation/aspect/AuthorizationAspectTest.java @@ -1,6 +1,8 @@ package uk.gov.hmcts.opal.authorisation.aspect; import org.aspectj.lang.ProceedingJoinPoint; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoExtension; @@ -20,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -30,18 +32,19 @@ @ExtendWith(MockitoExtension.class) class AuthorizationAspectTest { + static final Role ROLE = Role.builder() + .businessUnitId((short) 123) + .businessUserId("BU123") + .permissions(Set.of( + Permission.builder() + .permissionId(54L) + .permissionName("Account Enquiry") + .build())) + .build(); static final UserState USER_STATE = UserState.builder() .userName("name") .userId(123L) - .roles(Set.of(Role.builder() - .businessUnitId((short) 123) - .businessUserId("BU123") - .permissions(Set.of( - Permission.builder() - .permissionId(54L) - .permissionName("Account Enquiry") - .build())) - .build())) + .roles(Set.of(ROLE)) .build(); @MockBean @@ -56,60 +59,113 @@ class AuthorizationAspectTest { @MockBean AuthorizedAnyRoleHasPermission authorizedAnyRoleHasPermission; + @MockBean + AuthorizedRoleHasPermission authorizedRoleHasPermission; + @Autowired AuthorizationAspect authorizationAspect; - @Test - void checkAuthorization_WhenAuthorizationHeaderMissing_ThrowsException() throws Throwable { - Object[] args = {"some argument"}; - when(joinPoint.getArgs()).thenReturn(args); - when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn(null); - when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY); - - assertThrows( - MissingRequestHeaderException.class, - () -> authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission) - ); - } - - @Test - void checkAuthorization_WhenUserHasPermission_ReturnsProceededObject() throws Throwable { - Object[] args = {"Bearer token"}; - when(joinPoint.getArgs()).thenReturn(args); - when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); - when(authorizationAspectService.getAuthorization("Bearer token")) - .thenReturn(Optional.of("Bearer token")); - when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); - when(joinPoint.proceed()).thenReturn(new Object()); - when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY); - - Object result = authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission); - - assertNotNull(result); - verify(joinPoint, times(1)).proceed(); + @Nested + class AuthorizedAnyRoleHasPermissionAspect { + @Test + void checkAuthorization_WhenAuthorizationHeaderMissing_ThrowsException() throws Throwable { + Object[] args = {"some argument"}; + when(joinPoint.getArgs()).thenReturn(args); + when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn(null); + when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY); + + Assertions.assertThrows( + MissingRequestHeaderException.class, + () -> authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission) + ); + } + + @Test + void checkAuthorization_WhenUserHasPermission_ReturnsProceededObject() throws Throwable { + Object[] args = {"Bearer token"}; + when(joinPoint.getArgs()).thenReturn(args); + when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); + when(authorizationAspectService.getAuthorization("Bearer token")) + .thenReturn(Optional.of("Bearer token")); + when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); + when(joinPoint.proceed()).thenReturn(new Object()); + when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY); + + Object result = authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission); + + assertNotNull(result); + verify(joinPoint, times(1)).proceed(); + } + + @Test + void checkAuthorization_WhenUserDoesNotHavePermission_ThrowsException() throws Throwable { + Object[] args = {"Bearer token"}; + when(joinPoint.getArgs()).thenReturn(args); + when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); + when(authorizationAspectService.getAuthorization("Bearer token")) + .thenReturn(Optional.of("Bearer token")); + when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); + when(joinPoint.proceed()).thenReturn(new Object()); + when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY_NOTES); + + AccessDeniedException exception = Assertions.assertThrows( + AccessDeniedException.class, + () -> authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission) + ); + + assertNotNull(exception); + assertEquals( + "User does not have the required permission: Account Enquiry - Account Notes", + exception.getMessage() + ); + verify(joinPoint, never()).proceed(); + } } - @Test - void checkAuthorization_WhenUserDoesNotHavePermission_ReturnsNull() throws Throwable { - Object[] args = {"Bearer token"}; - when(joinPoint.getArgs()).thenReturn(args); - when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); - when(authorizationAspectService.getAuthorization("Bearer token")) - .thenReturn(Optional.of("Bearer token")); - when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); - when(joinPoint.proceed()).thenReturn(new Object()); - when(authorizedAnyRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY_NOTES); - - AccessDeniedException exception = assertThrows( - AccessDeniedException.class, - () -> authorizationAspect.checkAuthorization(joinPoint, authorizedAnyRoleHasPermission) - ); - - assertNotNull(exception); - assertEquals( - "User does not have the required permission: Account Enquiry - Account Notes", - exception.getMessage() - ); - verify(joinPoint, never()).proceed(); + @Nested + class AuthorizedRoleHasPermissionAspect { + + @Test + void checkAuthorization_WhenUserHasPermission_ReturnsProceededObject() throws Throwable { + Object[] args = {"Bearer token"}; + when(joinPoint.getArgs()).thenReturn(args); + when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); + when(authorizationAspectService.getAuthorization("Bearer token")) + .thenReturn(Optional.of("Bearer token")); + when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); + when(joinPoint.proceed()).thenReturn(new Object()); + when(authorizedRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY); + when(authorizationAspectService.getRole(any(), any())).thenReturn(ROLE); + + Object result = authorizationAspect.checkAuthorization(joinPoint, authorizedRoleHasPermission); + + assertNotNull(result); + verify(joinPoint, times(1)).proceed(); + } + + @Test + void checkAuthorization_WhenUserDoesNotHavePermission_ThrowsException() throws Throwable { + Object[] args = {"Bearer token"}; + when(joinPoint.getArgs()).thenReturn(args); + when(authorizationAspectService.getRequestHeaderValue(args)).thenReturn("Bearer token"); + when(authorizationAspectService.getAuthorization("Bearer token")) + .thenReturn(Optional.of("Bearer token")); + when(userStateService.getUserStateUsingAuthToken("Bearer token")).thenReturn(USER_STATE); + when(joinPoint.proceed()).thenReturn(new Object()); + when(authorizedRoleHasPermission.value()).thenReturn(Permissions.ACCOUNT_ENQUIRY_NOTES); + when(authorizationAspectService.getRole(any(), any())).thenReturn(ROLE); + + AccessDeniedException exception = Assertions.assertThrows( + AccessDeniedException.class, + () -> authorizationAspect.checkAuthorization(joinPoint, authorizedRoleHasPermission) + ); + + assertNotNull(exception); + assertEquals( + "User does not have the required permission: Account Enquiry - Account Notes", + exception.getMessage() + ); + verify(joinPoint, never()).proceed(); + } } }