Skip to content

Commit 3ad9f77

Browse files
authored
Merge pull request #208 from gooddata/aole-test-gooddata-http-client1
Fix: authentication bypass vulnerability
2 parents c8d34af + 7cd4b57 commit 3ad9f77

File tree

3 files changed

+122
-2
lines changed

3 files changed

+122
-2
lines changed

pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,4 +255,4 @@
255255
</build>
256256
</profile>
257257
</profiles>
258-
</project>
258+
</project>

src/main/java/com/gooddata/http/client/GoodDataHttpClient.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,21 @@ public ClassicHttpResponse execute(HttpHost target, ClassicHttpRequest request)
332332

333333
public <T> T execute(HttpHost target, ClassicHttpRequest request, HttpContext context,
334334
HttpClientResponseHandler<? extends T> responseHandler) throws IOException, org.apache.hc.core5.http.HttpException {
335-
return httpClient.execute(target, request, context, responseHandler);
335+
336+
if (responseHandler == null) {
337+
throw new IllegalArgumentException("Response handler cannot be null");
338+
}
339+
340+
// First, execute with authentication (reuse existing logic from the other execute method)
341+
ClassicHttpResponse response = this.execute(target, request, context);
342+
343+
try {
344+
// Then apply the response handler
345+
return responseHandler.handleResponse(response);
346+
} finally {
347+
// Ensure response entity is properly consumed to release resources
348+
EntityUtils.consumeQuietly(response.getEntity());
349+
}
336350
}
337351
/**
338352
* Util for logout request check.

src/test/java/com/gooddata/http/client/GoodDataHttpClientTest.java

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,4 +412,110 @@ public Object answer(org.mockito.invocation.InvocationOnMock invocation) throws
412412
assertEquals(400, ex.getCode());
413413
assertEquals("bad request", ex.getReason());
414414
}
415+
416+
/**
417+
* Test that execute() with ResponseHandler parameter applies authentication tokens.
418+
* This verifies the fix for the critical bug where this method was bypassing authentication.
419+
*/
420+
@SuppressWarnings("unchecked")
421+
@Test
422+
public void execute_withResponseHandler_appliesAuthentication() throws Exception {
423+
// Prepare mock response
424+
when(httpClient.execute(eq(host), any(ClassicHttpRequest.class), (HttpContext) isNull(), any(HttpClientResponseHandler.class)))
425+
.thenAnswer(new Answer<Object>() {
426+
@Override
427+
public Object answer(org.mockito.invocation.InvocationOnMock invocation) throws Throwable {
428+
HttpClientResponseHandler<?> handler = invocation.getArgument(3);
429+
return handler.handleResponse(okResponse);
430+
}
431+
});
432+
433+
when(sstStrategy.obtainSst(httpClient, host)).thenReturn(SST);
434+
435+
// Set up authentication state by pre-populating TT token
436+
Field ttField = GoodDataHttpClient.class.getDeclaredField("tt");
437+
ttField.setAccessible(true);
438+
ttField.set(goodDataHttpClient, TT);
439+
440+
Field sstField = GoodDataHttpClient.class.getDeclaredField("sst");
441+
sstField.setAccessible(true);
442+
sstField.set(goodDataHttpClient, SST);
443+
444+
// Create test request
445+
HttpGet request = new HttpGet("/gdc/account/profile/current");
446+
447+
// Execute with ResponseHandler
448+
String result = goodDataHttpClient.execute(host, request, null, response -> {
449+
// Verify the response handler receives the response
450+
assertEquals(200, response.getCode());
451+
return "success";
452+
});
453+
454+
// Verify result
455+
assertEquals("success", result);
456+
457+
// Verify that X-GDC-AuthTT header was added to the request
458+
// This verifies authentication was applied before sending the request
459+
Header[] headers = request.getHeaders("X-GDC-AuthTT");
460+
assertEquals(1, headers.length, "X-GDC-AuthTT header should be present");
461+
assertEquals(TT, headers[0].getValue(), "X-GDC-AuthTT header should contain the token");
462+
}
463+
464+
/**
465+
* Test that execute() with ResponseHandler handles 401 responses by refreshing tokens.
466+
*/
467+
@SuppressWarnings("unchecked")
468+
@Test
469+
public void execute_withResponseHandler_handles401WithTokenRefresh() throws Exception {
470+
// Prepare mock sequence: first 401, then success after token refresh
471+
when(httpClient.execute(eq(host), any(ClassicHttpRequest.class), (HttpContext) isNull(), any(HttpClientResponseHandler.class)))
472+
.thenAnswer(new Answer<Object>() {
473+
private int count = 0;
474+
@Override
475+
public Object answer(org.mockito.invocation.InvocationOnMock invocation) throws Throwable {
476+
HttpClientResponseHandler<?> handler = invocation.getArgument(3);
477+
count++;
478+
if (count == 1) {
479+
// First call returns 401
480+
return handler.handleResponse(ttChallengeResponse);
481+
} else if (count == 2) {
482+
// Token refresh call
483+
return handler.handleResponse(ttRefreshedResponse);
484+
} else {
485+
// Retry returns 200
486+
return handler.handleResponse(okResponse);
487+
}
488+
}
489+
});
490+
491+
when(sstStrategy.obtainSst(httpClient, host)).thenReturn(SST);
492+
493+
// Create test request
494+
HttpGet request = new HttpGet("/gdc/account/profile/current");
495+
496+
// Execute with ResponseHandler (should handle 401 and retry)
497+
String result = goodDataHttpClient.execute(host, request, null, response -> {
498+
assertEquals(200, response.getCode(), "Should eventually receive 200 after token refresh");
499+
return "success_after_refresh";
500+
});
501+
502+
// Verify result
503+
assertEquals("success_after_refresh", result);
504+
}
505+
506+
/**
507+
* Test that execute() with null ResponseHandler throws IllegalArgumentException.
508+
*/
509+
@Test
510+
public void execute_withNullResponseHandler_throwsException() throws Exception {
511+
HttpGet request = new HttpGet("/gdc/account/profile/current");
512+
513+
// Execute with null ResponseHandler should throw
514+
IllegalArgumentException ex = assertThrows(
515+
IllegalArgumentException.class,
516+
() -> goodDataHttpClient.execute(host, request, null, null)
517+
);
518+
519+
assertTrue(ex.getMessage().contains("Response handler cannot be null"));
520+
}
415521
}

0 commit comments

Comments
 (0)