Skip to content

Commit acfb11b

Browse files
committed
Fix: Propagate credential refresh exceptions in blocking refresh.
1 parent 13fd80f commit acfb11b

File tree

2 files changed

+54
-26
lines changed

2 files changed

+54
-26
lines changed

cab-token-generator/java/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactory.java

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -123,29 +123,26 @@ void refreshCredentialsIfRequired() throws IOException {
123123
switch (refreshType) {
124124
case BLOCKING:
125125
if (refreshTask.isNew) {
126-
// Execute the new refresh task synchronously on a direct executor.
127-
// This blocks until the refresh is complete.
126+
// Start a new refresh task only if the task is new
128127
MoreExecutors.directExecutor().execute(refreshTask.task);
129-
} else {
130-
// A refresh is already in progress, wait for it to complete.
131-
try {
132-
refreshTask.task.get();
133-
} catch (InterruptedException e) {
134-
// Restore the interrupted status and throw an exception.
135-
Thread.currentThread().interrupt();
136-
throw new IOException(
137-
"Interrupted while asynchronously refreshing the intermediate credentials", e);
138-
} catch (ExecutionException e) {
139-
// Unwrap the underlying cause of the execution exception.
140-
Throwable cause = e.getCause();
141-
if (cause instanceof IOException) {
142-
throw (IOException) cause;
143-
} else if (cause instanceof RuntimeException) {
144-
throw (RuntimeException) cause;
145-
} else {
146-
// Wrap other exceptions in an IOException.
147-
throw new IOException("Unexpected error refreshing intermediate credentials", cause);
148-
}
128+
}
129+
try {
130+
refreshTask.task.get(); // Wait for the refresh task to complete.
131+
} catch (InterruptedException e) {
132+
// Restore the interrupted status and throw an exception.
133+
Thread.currentThread().interrupt();
134+
throw new IOException(
135+
"Interrupted while asynchronously refreshing the intermediate credentials", e);
136+
} catch (ExecutionException e) {
137+
// Unwrap the underlying cause of the execution exception.
138+
Throwable cause = e.getCause();
139+
if (cause instanceof IOException) {
140+
throw (IOException) cause;
141+
} else if (cause instanceof RuntimeException) {
142+
throw (RuntimeException) cause;
143+
} else {
144+
// Wrap other exceptions in an IOException.
145+
throw new IOException("Unexpected error refreshing intermediate credentials", cause);
149146
}
150147
}
151148
break;
@@ -296,12 +293,11 @@ private static AccessToken getTokenFromResponse(
296293
* exceptions during the refresh are caught and suppressed to prevent indefinite blocking of
297294
* subsequent refresh attempts.
298295
*/
299-
private void finishRefreshTask(ListenableFuture<IntermediateCredentials> finishedTask) {
296+
private void finishRefreshTask(ListenableFuture<IntermediateCredentials> finishedTask)
297+
throws ExecutionException {
300298
synchronized (refreshLock) {
301299
try {
302300
this.intermediateCredentials = Futures.getDone(finishedTask);
303-
} catch (Exception e) {
304-
// noop
305301
} finally {
306302
if (this.refreshTask != null && this.refreshTask.task == finishedTask) {
307303
this.refreshTask = null;
@@ -372,7 +368,16 @@ class RefreshTask extends AbstractFuture<IntermediateCredentials> implements Run
372368
this.isNew = isNew;
373369

374370
// Add listener to update factory's credentials when the task completes.
375-
task.addListener(() -> finishRefreshTask(task), MoreExecutors.directExecutor());
371+
task.addListener(
372+
() -> {
373+
try {
374+
finishRefreshTask(task);
375+
} catch (ExecutionException e) {
376+
Throwable cause = e.getCause();
377+
RefreshTask.this.setException(cause);
378+
}
379+
},
380+
MoreExecutors.directExecutor());
376381

377382
// Add callback to set the result or exception based on the outcome.
378383
Futures.addCallback(

cab-token-generator/javatests/com/google/auth/credentialaccessboundary/ClientSideCredentialAccessBoundaryFactoryTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,29 @@ public void refreshCredentialsIfRequired_asyncMultiThread()
347347
assertEquals(2, mockStsTransportFactory.transport.getRequestCount());
348348
}
349349

350+
@Test
351+
public void refreshCredentialsIfRequired_sourceCredentialCannotRefresh_throwsIOException()
352+
throws Exception {
353+
// Simulate error when refreshing the source credential.
354+
mockTokenServerTransportFactory.transport.setError(new IOException());
355+
356+
GoogleCredentials sourceCredentials =
357+
getServiceAccountSourceCredentials(mockTokenServerTransportFactory);
358+
359+
ClientSideCredentialAccessBoundaryFactory factory =
360+
ClientSideCredentialAccessBoundaryFactory.newBuilder()
361+
.setSourceCredential(sourceCredentials)
362+
.setHttpTransportFactory(mockStsTransportFactory)
363+
.build();
364+
365+
try {
366+
factory.refreshCredentialsIfRequired(); // Expecting an IOException
367+
fail("Should fail as the source credential should not be able to be refreshed.");
368+
} catch (IOException e) {
369+
assertEquals("Unable to refresh the provided source credential.", e.getMessage());
370+
}
371+
}
372+
350373
// Tests related to the builder methods
351374
@Test
352375
public void builder_noSourceCredential_throws() {

0 commit comments

Comments
 (0)