Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/
package org.zowe.apiml.gateway.filters.pre;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import lombok.RequiredArgsConstructor;
import org.zowe.apiml.gateway.security.service.AuthenticationService;
import org.zowe.apiml.security.common.token.TokenAuthentication;

import java.util.Optional;

import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.PRE_TYPE;
import static org.springframework.cloud.netflix.zuul.filters.support.FilterConstants.SERVLET_DETECTION_FILTER_ORDER;

/**
* This is the very first filter in ZUUL. It verify JWT using on a call. In case of missing any JWT token it pass
* through (non secure calls). Otherwise filter checks token validity. If JWT is valid it does nothing too. If
* JWT is not valid or unsigned it returns response code 401 to user.
*/
@RequiredArgsConstructor
public class JwtValidatorFilter extends ZuulFilter {

private final AuthenticationService authenticationService;

@Override
public String filterType() {
return PRE_TYPE;
}

@Override
public int filterOrder() {
return SERVLET_DETECTION_FILTER_ORDER - 10;
}

@Override
public boolean shouldFilter() {
return true;
}

@Override
public Object run() {
final RequestContext requestContext = RequestContext.getCurrentContext();

final Optional<String> jwtToken = authenticationService.getJwtTokenFromRequest(requestContext.getRequest());
if (jwtToken.isPresent()) {
final TokenAuthentication tokenAuthentication = authenticationService.validateJwtToken(jwtToken.get());
if (!tokenAuthentication.isAuthenticated()) {
requestContext.setSendZuulResponse(false);
requestContext.setResponseStatusCode(SC_UNAUTHORIZED);
}
}

return null;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.zowe.apiml.gateway.filters.post.ConvertAuthTokenInUriToCookieFilter;
import org.zowe.apiml.gateway.filters.post.PageRedirectionFilter;
import org.zowe.apiml.gateway.filters.pre.*;
import org.zowe.apiml.gateway.security.service.AuthenticationService;
import org.zowe.apiml.gateway.ws.WebSocketProxyServerHandler;
import org.zowe.apiml.message.core.MessageService;
import org.zowe.apiml.product.gateway.GatewayConfigProperties;
Expand All @@ -42,6 +43,11 @@ public EncodedCharactersFilter encodedCharactersFilter(DiscoveryClient discovery
return new EncodedCharactersFilter(discovery, messageService);
}

@Bean
public JwtValidatorFilter jwtValidatorFilter(AuthenticationService authenticationService) {
return new JwtValidatorFilter(authenticationService);
}

@Bean
public SlashFilter slashFilter() {
return new SlashFilter();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package org.zowe.apiml.gateway.filters.pre;/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*/

import com.netflix.zuul.context.RequestContext;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.zowe.apiml.gateway.security.service.AuthenticationService;
import org.zowe.apiml.security.common.token.TokenAuthentication;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.*;

public class JwtValidatorFilterTest {

private static final String USERNAME = "username";
private static final String VALID_JWT = "validJwtToken";
private static final String INVALID_JWT = "invalidJwtToken";

private AuthenticationService authenticationService;
private JwtValidatorFilter jwtValidatorFilter;
private RequestContext requestContext;

@BeforeEach
public void initTest() {
authenticationService = mock(AuthenticationService.class);
jwtValidatorFilter = new JwtValidatorFilter(authenticationService);

when(authenticationService.validateJwtToken(VALID_JWT))
.thenReturn(TokenAuthentication.createAuthenticated(USERNAME, VALID_JWT));
when(authenticationService.validateJwtToken(INVALID_JWT))
.thenReturn(new TokenAuthentication(USERNAME, INVALID_JWT));

requestContext = mock(RequestContext.class);
RequestContext.testSetCurrentContext(requestContext);
}

@AfterEach
public void tearDown() {
RequestContext.testSetCurrentContext(null);
}

@Test
public void givenValidToken_thenSuccess_whenVerifyAccess() {
when(authenticationService.getJwtTokenFromRequest(any()))
.thenReturn(Optional.of(VALID_JWT));

jwtValidatorFilter.run();

verify(requestContext, never()).setSendZuulResponse(anyBoolean());
verify(requestContext, never()).setResponseStatusCode(anyInt());
}

@Test
public void givenInvalidToken_thenUnAthorized_whenVerifyAccess() {
when(authenticationService.getJwtTokenFromRequest(any()))
.thenReturn(Optional.of(INVALID_JWT));

jwtValidatorFilter.run();

verify(requestContext, times(1)).setSendZuulResponse(false);
verify(requestContext, times(1)).setResponseStatusCode(401);
}

@Test
public void notGivenToken_thenSuccess_whenVerifyAccess() {
when(authenticationService.getJwtTokenFromRequest(any()))
.thenReturn(Optional.empty());

jwtValidatorFilter.run();

verify(requestContext, never()).setSendZuulResponse(anyBoolean());
verify(requestContext, never()).setResponseStatusCode(anyInt());
}

@Test
public void testFilterConfiguration() {
assertEquals("pre", jwtValidatorFilter.filterType());
assertTrue(jwtValidatorFilter.shouldFilter());
assertTrue(jwtValidatorFilter.filterOrder() < 0);
}

}
2 changes: 2 additions & 0 deletions integration-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,8 @@ task runTestWithoutStartupCheck(type: Test) {
group "Integration tests"
description "Run integration test without startup check"

useJUnitPlatform()

systemProperties System.properties

outputs.upToDateWhen { false }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
*/
package org.zowe.apiml.apicatalog;

import org.zowe.apiml.gatewayservice.SecurityUtils;
import org.zowe.apiml.util.config.ConfigReader;
import io.restassured.RestAssured;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.springframework.http.HttpHeaders;
import org.zowe.apiml.gatewayservice.SecurityUtils;
import org.zowe.apiml.util.config.ConfigReader;
import org.zowe.apiml.util.service.DiscoveryUtils;

import java.util.Arrays;
import java.util.Collection;
Expand All @@ -26,6 +27,7 @@
import static org.apache.http.HttpStatus.SC_UNAUTHORIZED;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.core.Is.is;
import static org.zowe.apiml.gatewayservice.SecurityUtils.getConfiguredSslConfig;

@RunWith(value = Parameterized.class)
public class ApiCatalogSecurityIntegrationTest {
Expand Down Expand Up @@ -114,25 +116,37 @@ public void accessProtectedEndpointWithInvalidBasicAuth() {
.get(String.format("%s://%s:%d%s%s%s", GATEWAY_SCHEME, GATEWAY_HOST, GATEWAY_PORT, CATALOG_PREFIX,
CATALOG_SERVICE_ID, endpoint))
.then()
.statusCode(is(SC_UNAUTHORIZED))
.body("messages.find { it.messageNumber == 'ZWEAS120E' }.messageContent", equalTo(expectedMessage)
);
.statusCode(is(SC_UNAUTHORIZED));
}

@Test
public void accessProtectedEndpointWithInvalidCookie() {
public void accessProtectedEndpointWithInvalidCookieCatalog() {
String expectedMessage = "Token is not valid for URL '" + CATALOG_SERVICE_ID + endpoint + "'";
String invalidToken = "nonsense";

RestAssured.config = RestAssured.config().sslConfig(getConfiguredSslConfig());
String catalogUrl = DiscoveryUtils.getInstances("APICATALOG").get(0).getUrl();

given()
.cookie(COOKIE, invalidToken)
.when()
.get(String.format("%s%s%s", catalogUrl, CATALOG_SERVICE_ID, endpoint))
.then()
.statusCode(is(SC_UNAUTHORIZED))
.body("messages.find { it.messageNumber == 'ZWEAS130E' }.messageContent", equalTo(expectedMessage));
}

@Test
public void accessProtectedEndpointWithInvalidCookieGateway() {
String invalidToken = "nonsense";

given()
.cookie(COOKIE, invalidToken)
.when()
.get(String.format("%s://%s:%d%s%s%s", GATEWAY_SCHEME, GATEWAY_HOST, GATEWAY_PORT, CATALOG_PREFIX,
CATALOG_SERVICE_ID, endpoint))
.then()
.statusCode(is(SC_UNAUTHORIZED))
.body("messages.find { it.messageNumber == 'ZWEAS130E' }.messageContent", equalTo(expectedMessage)
);
.statusCode(is(SC_UNAUTHORIZED));
}

//@formatter:on
Expand Down
Loading