diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 41d107c3db..55541e4a44 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -208,7 +208,11 @@ jobs: APIML_SECURITY_AUTH_PROVIDER: saf APIML_DISCOVERY_ALLPEERSURLS: https://apiml-2:10011/eureka,https://apiml:10011/eureka APIML_SERVICE_HOSTNAME: apiml + CACHING_STORAGE_INFINISPAN_INITIALHOSTS: "apiml[7600],apiml-2[7600]" logbackService: ZWEAGW1 + JGROUPS_BIND_PORT: 7600 + JGROUPS_BIND_ADDRESS: apiml + JGROUPS_KEYEXCHANGE_PORT: 7601 apiml-2: image: ghcr.io/balhar-jakub/apiml:${{ github.run_id }}-${{ github.run_number }} env: @@ -223,7 +227,11 @@ jobs: APIML_SECURITY_AUTH_PROVIDER: saf APIML_DISCOVERY_ALLPEERSURLS: https://apiml:10011/eureka,https://apiml-2:10011/eureka APIML_SERVICE_HOSTNAME: apiml-2 + CACHING_STORAGE_INFINISPAN_INITIALHOSTS: "apiml[7600],apiml-2[7600]" logbackService: ZWEAGW2 + JGROUPS_BIND_PORT: 7600 + JGROUPS_BIND_ADDRESS: apiml-2 + JGROUPS_KEYEXCHANGE_PORT: 7601 discoverable-client: image: ghcr.io/balhar-jakub/discoverable-client:${{ github.run_id }}-${{ github.run_number }} env: diff --git a/apiml/src/main/java/org/zowe/apiml/filter/LogoutHandler.java b/apiml/src/main/java/org/zowe/apiml/filter/LogoutHandler.java index 083969cd82..10ef6754b9 100644 --- a/apiml/src/main/java/org/zowe/apiml/filter/LogoutHandler.java +++ b/apiml/src/main/java/org/zowe/apiml/filter/LogoutHandler.java @@ -11,7 +11,9 @@ package org.zowe.apiml.filter; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl; +import jakarta.annotation.PostConstruct; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationContext; import org.springframework.http.HttpHeaders; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -38,6 +40,14 @@ public class LogoutHandler implements ServerLogoutHandler { private final FailedAuthenticationWebHandler failure; private final PeerAwareInstanceRegistryImpl peerAwareInstanceRegistry; private final HttpUtils httpUtils; + private final ApplicationContext applicationContext; + + private boolean distribute; + + @PostConstruct + void init() { + distribute = !applicationContext.containsBean("infinispanConfig"); + } @Override public Mono logout(WebFilterExchange exchange, Authentication authentication) { @@ -51,21 +61,21 @@ private Mono invalidateJwtToken(String token, WebFilterExchange exchange) ); } - if (Boolean.TRUE.equals(authenticationService.isInvalidated(token))) { - return failure.onAuthenticationFailure(exchange,new TokenNotValidException("The token you are trying to logout is not valid")); + if (authenticationService.isInvalidated(token)) { + return failure.onAuthenticationFailure(exchange, new TokenNotValidException("The token you are trying to logout is not valid")); } else { try { var app = peerAwareInstanceRegistry.getApplications().getRegisteredApplications(CoreService.GATEWAY.getServiceId()); - authenticationService.invalidateJwtTokenGateway(token, true, app); + authenticationService.invalidateJwtTokenGateway(token, distribute, app); } catch (TokenNotValidException e) { // TokenNotValidException thrown in cases where the format is not valid - return failure.onAuthenticationFailure(exchange,new TokenFormatNotValidException(e.getMessage())); + return failure.onAuthenticationFailure(exchange, new TokenFormatNotValidException(e.getMessage())); } catch (AuthenticationException e) { return failure.onAuthenticationFailure(exchange, e); } catch (Exception e) { // Catch any issue like ServiceNotAccessibleException, throw TokenNotValidException // so a 401 is returned. Returning 500 gives information about the system and is thus avoided. - return failure.onAuthenticationFailure(exchange, new TokenNotValidException("Error while logging out token")); + return failure.onAuthenticationFailure(exchange, new TokenNotValidException("Error while logging out token")); } return Mono.empty(); } diff --git a/apiml/src/test/java/org/zowe/apiml/filter/LogoutHandlerTest.java b/apiml/src/test/java/org/zowe/apiml/filter/LogoutHandlerTest.java index 476273e13b..ddb166e52b 100644 --- a/apiml/src/test/java/org/zowe/apiml/filter/LogoutHandlerTest.java +++ b/apiml/src/test/java/org/zowe/apiml/filter/LogoutHandlerTest.java @@ -10,6 +10,7 @@ package org.zowe.apiml.filter; +import com.netflix.discovery.shared.Application; import com.netflix.discovery.shared.Applications; import com.netflix.eureka.registry.PeerAwareInstanceRegistryImpl; import org.junit.jupiter.api.BeforeEach; @@ -17,6 +18,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationContext; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; @@ -25,6 +27,7 @@ import org.springframework.security.web.server.WebFilterExchange; import org.springframework.web.server.WebFilterChain; import org.zowe.apiml.handler.FailedAuthenticationWebHandler; +import org.zowe.apiml.product.constants.CoreService; import org.zowe.apiml.security.common.config.AuthConfigurationProperties; import org.zowe.apiml.security.common.token.TokenFormatNotValidException; import org.zowe.apiml.security.common.token.TokenNotValidException; @@ -46,6 +49,7 @@ class LogoutHandlerTest { @Mock private AuthenticationService authenticationService; @Mock private FailedAuthenticationWebHandler failureHandler; @Mock private PeerAwareInstanceRegistryImpl registry; + @Mock private ApplicationContext applicationContext; private HttpUtils httpUtils = new HttpUtils(new AuthConfigurationProperties()) { { readConfig(); @@ -56,7 +60,7 @@ class LogoutHandlerTest { @BeforeEach void setUp() { - logoutHandler = new LogoutHandler(authenticationService, failureHandler, registry, httpUtils); + logoutHandler = new LogoutHandler(authenticationService, failureHandler, registry, httpUtils, applicationContext); } @Test @@ -119,7 +123,7 @@ void shouldCallFailureHandlerOnUnexpectedException() { } @Test - void shouldInvalidateValidTokenSuccessfully() { + void shouldInvalidateValidTokenSuccessfully_WithInfinispan() { var request = MockServerHttpRequest.get("/logout") .header(HttpHeaders.AUTHORIZATION, "Bearer token123") .build(); @@ -127,14 +131,41 @@ void shouldInvalidateValidTokenSuccessfully() { WebFilterChain mockChain = mock(WebFilterChain.class); var webFilterExchange = new WebFilterExchange(exchange, mockChain); + when(applicationContext.containsBean("infinispanConfig")).thenReturn(true); + logoutHandler.init(); + when(authenticationService.isInvalidated("token123")).thenReturn(false); Applications mockApplications = mock(Applications.class); when(registry.getApplications()).thenReturn(mockApplications); + var application = mock(Application.class); + when(mockApplications.getRegisteredApplications(CoreService.GATEWAY.getServiceId())).thenReturn(application); + StepVerifier.create(logoutHandler.logout(webFilterExchange, mock(Authentication.class))) + .verifyComplete(); + + verify(authenticationService).invalidateJwtTokenGateway(eq("token123"), eq(false), eq(application)); + } + @Test + void shouldInvalidateValidTokenSuccessfully_WithoutInfinispan() { + var request = MockServerHttpRequest.get("/logout") + .header(HttpHeaders.AUTHORIZATION, "Bearer token123") + .build(); + var exchange = MockServerWebExchange.from(request); + WebFilterChain mockChain = mock(WebFilterChain.class); + var webFilterExchange = new WebFilterExchange(exchange, mockChain); + + when(applicationContext.containsBean("infinispanConfig")).thenReturn(false); + logoutHandler.init(); + + when(authenticationService.isInvalidated("token123")).thenReturn(false); + Applications mockApplications = mock(Applications.class); + when(registry.getApplications()).thenReturn(mockApplications); + var application = mock(Application.class); + when(mockApplications.getRegisteredApplications(CoreService.GATEWAY.getServiceId())).thenReturn(application); StepVerifier.create(logoutHandler.logout(webFilterExchange, mock(Authentication.class))) .verifyComplete(); - verify(authenticationService).invalidateJwtTokenGateway(eq("token123"), eq(true), any()); + verify(authenticationService).invalidateJwtTokenGateway(eq("token123"), eq(true), eq(application)); } @Test