From 448f613a15a9f12951a560be12d637420185255a Mon Sep 17 00:00:00 2001 From: Yauhen Vavilkin Date: Thu, 24 Oct 2024 14:50:06 +0400 Subject: [PATCH 1/2] MODLOGINKC-35: fix error code while 401 in Keycloak --- .../login/controller/ApiExceptionHandler.java | 9 +++++++++ .../exception/UnauthorizedException.java | 14 ++++++++++++++ .../folio/login/service/KeycloakService.java | 3 +++ .../swagger.api/schemas/errorCode.json | 3 ++- .../java/org/folio/login/it/LogEventsIT.java | 2 +- .../login/service/KeycloakServiceTest.java | 19 +++++++++++++++++++ 6 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 src/main/java/org/folio/login/exception/UnauthorizedException.java diff --git a/src/main/java/org/folio/login/controller/ApiExceptionHandler.java b/src/main/java/org/folio/login/controller/ApiExceptionHandler.java index 6f7fc7a..529c05a 100644 --- a/src/main/java/org/folio/login/controller/ApiExceptionHandler.java +++ b/src/main/java/org/folio/login/controller/ApiExceptionHandler.java @@ -11,12 +11,14 @@ import static org.folio.login.domain.dto.ErrorCode.TOKEN_LOGOUT_UNPROCESSABLE; import static org.folio.login.domain.dto.ErrorCode.TOKEN_PARSE_FAILURE; import static org.folio.login.domain.dto.ErrorCode.TOKEN_REFRESH_UNPROCESSABLE; +import static org.folio.login.domain.dto.ErrorCode.UNAUTHORIZED_ERROR; import static org.folio.login.domain.dto.ErrorCode.UNKNOWN_ERROR; import static org.folio.login.domain.dto.ErrorCode.VALIDATION_ERROR; import static org.springframework.http.HttpStatus.BAD_REQUEST; import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NOT_FOUND; import static org.springframework.http.HttpStatus.NOT_IMPLEMENTED; +import static org.springframework.http.HttpStatus.UNAUTHORIZED; import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY; import jakarta.persistence.EntityExistsException; @@ -38,6 +40,7 @@ import org.folio.login.exception.TokenLogoutException; import org.folio.login.exception.TokenParsingException; import org.folio.login.exception.TokenRefreshException; +import org.folio.login.exception.UnauthorizedException; import org.folio.spring.cql.CqlQueryValidationException; import org.folio.spring.exception.NotFoundException; import org.springframework.dao.InvalidDataAccessApiUsageException; @@ -298,6 +301,12 @@ public ResponseEntity handleAllOtherExceptions(Exception exceptio return buildResponseEntity(exception, INTERNAL_SERVER_ERROR, UNKNOWN_ERROR); } + @ExceptionHandler(UnauthorizedException.class) + public ResponseEntity handleUnauthorizedException(UnauthorizedException exception) { + logException(DEBUG, exception); + return buildResponseEntity(exception, UNAUTHORIZED, UNAUTHORIZED_ERROR); + } + private static ErrorResponse buildValidationError(Exception exception, List parameters) { return buildErrorResponse(exception, parameters, VALIDATION_ERROR); } diff --git a/src/main/java/org/folio/login/exception/UnauthorizedException.java b/src/main/java/org/folio/login/exception/UnauthorizedException.java new file mode 100644 index 0000000..02a3281 --- /dev/null +++ b/src/main/java/org/folio/login/exception/UnauthorizedException.java @@ -0,0 +1,14 @@ +package org.folio.login.exception; + +public class UnauthorizedException extends RuntimeException { + + /** + * Creates {@link org.folio.login.exception.UnauthorizedException} with error message and error cause. + * + * @param message - error message as {@link String} object + * @param cause - error cause as {@link Throwable} object + */ + public UnauthorizedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/org/folio/login/service/KeycloakService.java b/src/main/java/org/folio/login/service/KeycloakService.java index 9027c20..7b34b16 100644 --- a/src/main/java/org/folio/login/service/KeycloakService.java +++ b/src/main/java/org/folio/login/service/KeycloakService.java @@ -28,6 +28,7 @@ import org.folio.login.domain.model.UserCredentials; import org.folio.login.exception.RequestValidationException; import org.folio.login.exception.ServiceException; +import org.folio.login.exception.UnauthorizedException; import org.folio.login.integration.kafka.LogoutEventPublisher; import org.folio.login.integration.keycloak.KeycloakClient; import org.folio.login.util.TokenRequestHelper; @@ -184,6 +185,8 @@ private KeycloakAuthentication getToken(String userAgent, String forwardedFor, M var tenantId = folioExecutionContext.getTenantId(); try { return keycloakClient.callTokenEndpoint(tenantId, payload, userAgent, forwardedFor); + } catch (FeignException.Unauthorized e) { + throw new UnauthorizedException("Unauthorized error", e); } catch (FeignException cause) { throw new ServiceException("Failed to obtain a token", cause); } diff --git a/src/main/resources/swagger.api/schemas/errorCode.json b/src/main/resources/swagger.api/schemas/errorCode.json index 45d0bb9..8b81f3e 100644 --- a/src/main/resources/swagger.api/schemas/errorCode.json +++ b/src/main/resources/swagger.api/schemas/errorCode.json @@ -12,6 +12,7 @@ "found_error", "token.parse.failure", "token.refresh.unprocessable", - "token.logout.unprocessable" + "token.logout.unprocessable", + "unauthorized_error" ] } diff --git a/src/test/java/org/folio/login/it/LogEventsIT.java b/src/test/java/org/folio/login/it/LogEventsIT.java index d1b6d6e..590933a 100644 --- a/src/test/java/org/folio/login/it/LogEventsIT.java +++ b/src/test/java/org/folio/login/it/LogEventsIT.java @@ -132,7 +132,7 @@ private static void callLoginEndpointWithInvalidCreds() { .contentType(APPLICATION_JSON) .header(XOkapiHeaders.TENANT, TENANT) .content(asJsonString(invalidLoginCredentials()))) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnauthorized()); } @SneakyThrows diff --git a/src/test/java/org/folio/login/service/KeycloakServiceTest.java b/src/test/java/org/folio/login/service/KeycloakServiceTest.java index 5bb0ddf..2d61940 100644 --- a/src/test/java/org/folio/login/service/KeycloakServiceTest.java +++ b/src/test/java/org/folio/login/service/KeycloakServiceTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import feign.FeignException; import feign.RetryableException; import jakarta.persistence.EntityNotFoundException; import jakarta.ws.rs.core.Form; @@ -48,6 +49,7 @@ import org.folio.login.domain.model.UserCredentials; import org.folio.login.exception.RequestValidationException; import org.folio.login.exception.ServiceException; +import org.folio.login.exception.UnauthorizedException; import org.folio.login.integration.kafka.LogoutEventPublisher; import org.folio.login.integration.keycloak.KeycloakClient; import org.folio.login.support.TestConstants; @@ -136,6 +138,23 @@ void getUserToken_negative_KeycloakError() { .hasMessage("Failed to obtain a token"); } + @Test + void getUserToken_negative_unauthorizedException() { + var requestData = loginRequest(USERNAME, PASSWORD, CLIENT_ID, CLIENT_SECRET); + var realmConfig = keycloakRealmConfiguration(); + + when(folioExecutionContext.getTenantId()).thenReturn(TENANT); + when(realmConfigurationProvider.getRealmConfiguration()).thenReturn(realmConfig); + when(keycloakClient.callTokenEndpoint(TENANT, requestData, null, null)) + .thenThrow(FeignException.Unauthorized.class); + + var credentials = loginCredentials(); + assertThatThrownBy(() -> keycloakService.getUserToken(credentials, null, null)) + .isInstanceOf(UnauthorizedException.class) + .hasMessage("Unauthorized error"); + } + + @Test void updateCredentials_positive() { changePassword(); From ea9fb9ef3ee8a1412bf5fd6b05f580ba03082635 Mon Sep 17 00:00:00 2001 From: Yauhen Vavilkin Date: Thu, 24 Oct 2024 15:05:24 +0400 Subject: [PATCH 2/2] MODLOGINKC-35: fix test --- src/test/java/org/folio/login/it/LoginAttemptsIT.java | 2 +- src/test/java/org/folio/login/service/KeycloakServiceTest.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/folio/login/it/LoginAttemptsIT.java b/src/test/java/org/folio/login/it/LoginAttemptsIT.java index 3d4e1ea..cd35075 100644 --- a/src/test/java/org/folio/login/it/LoginAttemptsIT.java +++ b/src/test/java/org/folio/login/it/LoginAttemptsIT.java @@ -95,7 +95,7 @@ private static void callLoginEndpointWithInvalidCreds() throws Exception { .contentType(APPLICATION_JSON) .header(XOkapiHeaders.TENANT, TENANT) .content(asJsonString(invalidLoginCredentials()))) - .andExpect(status().isBadRequest()); + .andExpect(status().isUnauthorized()); } @SneakyThrows diff --git a/src/test/java/org/folio/login/service/KeycloakServiceTest.java b/src/test/java/org/folio/login/service/KeycloakServiceTest.java index 2d61940..c186c3d 100644 --- a/src/test/java/org/folio/login/service/KeycloakServiceTest.java +++ b/src/test/java/org/folio/login/service/KeycloakServiceTest.java @@ -154,7 +154,6 @@ void getUserToken_negative_unauthorizedException() { .hasMessage("Unauthorized error"); } - @Test void updateCredentials_positive() { changePassword();