Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: authentication header for routed requests #4000

Merged
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;

import static org.zowe.apiml.constants.ApimlConstants.BEARER_AUTHENTICATION_PREFIX;

public abstract class AbstractTokenFilterFactory<T extends AbstractTokenFilterFactory.Config, D> extends AbstractAuthSchemeFactory<T, ZaasTokenResponse, D> {

protected AbstractTokenFilterFactory(Class<T> configClazz, WebClient webClient, InstanceInfoService instanceInfoService, MessageService messageService) {
Expand Down Expand Up @@ -90,6 +92,7 @@ protected Mono<Void> processResponse(ServerWebExchange exchange, GatewayFilterCh
response.get().getToken()
);
headers.set(HttpHeaders.COOKIE, cookieHeader);
headers.set(HttpHeaders.AUTHORIZATION, BEARER_AUTHENTICATION_PREFIX + " " + response.get().getToken());
}).build();
exchange = exchange.mutate().request(request).build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@

import com.sun.net.httpserver.HttpExchange;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
import org.junit.jupiter.api.*;
import org.springframework.http.HttpHeaders;
import org.zowe.apiml.auth.AuthenticationScheme;
import org.zowe.apiml.gateway.acceptance.common.AcceptanceTest;
Expand Down Expand Up @@ -262,7 +259,7 @@ class ZaasCommunication extends AcceptanceTestWithMockServices {
private MockService service;

@BeforeAll
void initService() throws IOException {
void initService() {
service = createService();
}

Expand Down Expand Up @@ -303,8 +300,9 @@ MockService createService() {
.authenticationScheme(getAuthenticationScheme())
.addEndpoint("/service/test/success")
.assertion(he -> assertEquals(JWT, getCookie(he, COOKIE_NAME)))
.assertion(he -> assertEquals(1, he.getRequestHeaders().get(HttpHeaders.AUTHORIZATION).size()))
.assertion(he -> assertEquals("Bearer " + JWT, he.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION)))

.assertion(he -> assertNull(he.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION)))
.assertion(he -> assertNull(he.getRequestHeaders().getFirst("x-service-id")))
.assertion(he -> assertNull(he.getRequestHeaders().getFirst("X-SAF-Token")))
.assertion(he -> assertNull(he.getRequestHeaders().getFirst("X-Certificate-Public")))
Expand All @@ -323,7 +321,8 @@ MockService createService() {
.addEndpoint("/service/test/fail")
.assertion(he -> assertNull(getCookie(he, COOKIE_NAME)))

.assertion(he -> assertNotNull(he.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION)))
.assertion(he -> assertEquals(1, he.getRequestHeaders().get(HttpHeaders.AUTHORIZATION).size()))
.assertion(he -> assertNotEquals("Bearer " + JWT, he.getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION)))
.assertion(he -> assertNull(he.getRequestHeaders().getFirst("x-service-id")))
.assertion(he -> assertNotNull(he.getRequestHeaders().getFirst("X-SAF-Token")))
.assertion(he -> assertNull(he.getRequestHeaders().getFirst("X-Certificate-Public")))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.mockito.ArgumentCaptor;
import org.springframework.cloud.client.ServiceInstance;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
Expand Down Expand Up @@ -79,6 +80,7 @@ void givenCookieResponse_whenHandling_thenUpdateTheRequest() {
.build()
));
assertEquals("cookieName=cookieValue", request.getHeaders().getFirst("cookie"));
assertEquals("Bearer cookieValue" , request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ static Stream<Arguments> schemas() {
var schemasTest = new ArrayList<Arguments>();
schemasTest.add(Arguments.of("zowejwt", HttpRequestUtils.getUriFromGateway(ZOWE_JWT_REQUEST), (Consumer<Response>) r -> {
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
assertNull(r.getBody().path("headers.authorization"));
assertNotNull(r.getBody().path("headers.authorization"));
assertThat(r.getBody().path("headers.cookie"), containsString(COOKIE_NAME));
String jwt = r.getBody().path("headers.cookie").toString();
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,12 +49,12 @@ static Stream<Arguments> validToBeTransformed() {
List<Arguments> arguments = new ArrayList<>(Arrays.asList(
Arguments.of("Zowe auth scheme", ZOWE_JWT_REQUEST, (Consumer<Response>) response -> {
assertNotNull(response.jsonPath().getString("cookies.apimlAuthenticationToken"), "Expected not null apimlAuthenticationToken. Response was: " + response.asPrettyString());
assertNull(response.jsonPath().getString("headers.authorization"), "Expected null Authorization header. Response was: " + response.asPrettyString());
assertNotNull(response.jsonPath().getString("headers.authorization"), "Expected not null Authorization header. Response was: " + response.asPrettyString());
assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs")), "Expected empty certs list. Response was: " + response.asPrettyString());
}),
Arguments.of("z/OSMF auth scheme", ZOSMF_REQUEST, (Consumer<Response>) response -> {
assertNotNull(response.jsonPath().getString("cookies.jwtToken"), "Expected not null jwtToken cookie. Response was: " + response.asPrettyString());
assertNull(response.jsonPath().getString("headers.authorization"), "Expected null Authorization header. Response was: " + response.asPrettyString());
assertNotNull(response.jsonPath().getString("headers.authorization"), "Expected not null Authorization header. Response was: " + response.asPrettyString());
assertTrue(CollectionUtils.isEmpty(response.jsonPath().getList("certs")), "Expected empty certs list. Response was: " + response.asPrettyString());
}),
Arguments.of("PassTicket auth scheme", REQUEST_INFO_ENDPOINT, (Consumer<Response>) response -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.zowe.apiml.util.categories.DiscoverableClientDependentTest;
import org.zowe.apiml.util.categories.InfinispanStorageTest;
import org.zowe.apiml.util.categories.zOSMFAuthTest;
import org.zowe.apiml.util.config.ConfigReader;
import org.zowe.apiml.util.config.ItSslConfigFactory;
import org.zowe.apiml.util.config.SslContext;
import org.zowe.apiml.util.http.HttpRequestUtils;
Expand All @@ -41,6 +42,9 @@
class ZoweJwtSchemeTest implements TestWithStartedInstances {

private static URI URL;
private static final String PASSWORD = ConfigReader.environmentConfiguration().getCredentials().getPassword();
private static final String USERNAME = ConfigReader.environmentConfiguration().getCredentials().getUser();


@BeforeAll
static void init() throws Exception {
Expand All @@ -56,6 +60,7 @@ void givenCorrectClientCertificateInRequest() {
.get(URL)
.then()
.body("headers.cookie", startsWith("apimlAuthenticationToken"))
.body("headers.authorization", startsWith("Bearer"))
.statusCode(200);
}

Expand All @@ -71,6 +76,20 @@ void givenInvalidClientCertificateInRequest() {
.statusCode(200);
}

@Test
void givenBasicAuthHeader_authenticationIsPassedUnchanged() {
given()
.config(SslContext.tlsWithoutCert)
.auth().preemptive().basic(USERNAME, PASSWORD)
.when()
.get(URL)
.then()
.log().all()
.body("headers.cookie", nullValue())
.body("headers.authorization", startsWith("Basic"))
.statusCode(200);
}

@Nested
class GivenCustomAuthHeader {
@Test
Expand Down Expand Up @@ -100,6 +119,7 @@ void forwardJWTToService() {
.get(URL)
.then()
.body("headers.cookie", is("apimlAuthenticationToken=" + jwt))
.body("headers.authorization", is("Bearer " + jwt))
.statusCode(200);
}

Expand All @@ -117,6 +137,7 @@ void preserveCookies() {
.then()
.body("cookies.apimlAuthenticationToken", is(jwt))
.body("cookies.XSRF-TOKEN", is("another-token-in-cookies"))
.body("headers.authorization", is("Bearer " + jwt))
.statusCode(200);
}

Expand Down Expand Up @@ -151,6 +172,7 @@ void translateIntoJWTAndSendToService() {
.get(URL)
.then()
.body("headers.cookie", startsWith("apimlAuthenticationToken="))
.body("headers.authorization", startsWith("Bearer "))
.statusCode(200);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ void viaBasicAuthHeader() {
URI uri = HttpRequestUtils.getUriFromGateway(ZOSMF_ENDPOINT, ARGUMENT);

given()
.auth().preemptive().basic(USERNAME, new String(PASSWORD))
.auth().preemptive().basic(USERNAME, PASSWORD)
.header("X-CSRF-ZOSMF-HEADER", "zosmf")
.when()
.get(uri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.zowe.apiml.client.model.LoginBody;
import org.zowe.apiml.client.services.JwtTokenService;

Expand Down Expand Up @@ -218,6 +219,19 @@ protected boolean isValidJwtCookie(Map<String, String> headers) {

}

protected boolean isValidAuthHeader(String authHeader) {
if (!StringUtils.hasText(authHeader)) {
return false;
}

if (authHeader.startsWith("Bearer")) {
var jwtToken = authHeader.length() > 8 ? authHeader.substring(7) : "";
return jwtTokenService.validateJwtToken(jwtToken);
}

return true;
}

private String getAuthCookie(Map<String, String> headers) {
return headers.get(COOKIE_HEADER) != null ? headers.get(COOKIE_HEADER) : headers.get(HttpHeaders.COOKIE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ protected ResponseEntity<?> handleFiles(Map<String, String> headers) {
String authorization = headers.get(AUTHORIZATION_HEADER);

if (authorization != null) {
if (authorization.startsWith("Bearer")) {
if (!isValidAuthHeader(authorization) && !ltpaIsPresent(headers)) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
} else {
Expand Down
Loading