@@ -15,6 +15,7 @@ import com.google.common.util.concurrent.UncheckedExecutionException
15
15
import com.google.gson.Gson
16
16
import com.google.gson.JsonSyntaxException
17
17
import dev.failsafe.Failsafe
18
+ import dev.failsafe.FailsafeException
18
19
import dev.failsafe.RetryPolicy
19
20
import dev.failsafe.event.EventListener
20
21
import dev.failsafe.event.ExecutionAttemptedEvent
@@ -62,6 +63,8 @@ class TowerFusionToken implements FusionToken {
62
63
private static final int DEFAULT_RETRY_POLICY_MAX_ATTEMPTS = 10
63
64
private static final double DEFAULT_RETRY_POLICY_JITTER = 0.5
64
65
66
+ private CookieManager cookieManager = new CookieManager ()
67
+
65
68
// The HttpClient instance used to send requests
66
69
private final HttpClient httpClient = newDefaultHttpClient()
67
70
@@ -80,7 +83,9 @@ class TowerFusionToken implements FusionToken {
80
83
private String endpoint
81
84
82
85
// Platform access token to use for requests
83
- private String accessToken
86
+ private volatile String accessToken
87
+
88
+ private volatile String refreshToken
84
89
85
90
// Platform workflowId
86
91
private String workspaceId
@@ -93,6 +98,7 @@ class TowerFusionToken implements FusionToken {
93
98
final env = SysEnv . get()
94
99
this . endpoint = PlatformHelper . getEndpoint(config, env)
95
100
this . accessToken = PlatformHelper . getAccessToken(config, env)
101
+ this . refreshToken = PlatformHelper . getRefreshToken(config, env)
96
102
this . workflowId = env. get(' TOWER_WORKFLOW_ID' )
97
103
this . workspaceId = PlatformHelper . getWorkspaceId(config, env)
98
104
}
@@ -182,11 +188,11 @@ class TowerFusionToken implements FusionToken {
182
188
* Create a new HttpClient instance with default settings
183
189
* @return The new HttpClient instance
184
190
*/
185
- private static HttpClient newDefaultHttpClient () {
191
+ private HttpClient newDefaultHttpClient () {
186
192
final builder = HttpClient . newBuilder()
187
193
.version(HttpClient.Version . HTTP_1_1 )
188
194
.followRedirects(HttpClient.Redirect . NEVER )
189
- .cookieHandler(new CookieManager () )
195
+ .cookieHandler(cookieManager )
190
196
.connectTimeout(DEFAULT_CONNECTION_TIMEOUT )
191
197
// use virtual threads executor if enabled
192
198
if ( Threads . useVirtual() ) {
@@ -236,8 +242,17 @@ class TowerFusionToken implements FusionToken {
236
242
* @param req The HttpRequest to send
237
243
* @return The HttpResponse received
238
244
*/
239
- private <T> HttpResponse<String > safeHttpSend (HttpRequest req , RetryPolicy<T> policy ) {
240
- return Failsafe . with(policy). get(
245
+ private <T> HttpResponse<String > safeHttpSend (HttpRequest req ) {
246
+ try {
247
+ safeApply(req)
248
+ }
249
+ catch (FailsafeException e) {
250
+ throw e. cause
251
+ }
252
+ }
253
+
254
+ private <T> HttpResponse<String > safeApply (HttpRequest req ) {
255
+ return Failsafe . with(retryPolicy). get(
241
256
() -> {
242
257
log. debug " Http request: method=${ req.method()} ; uri=${ req.uri()} ; request=${ req} "
243
258
final resp = httpClient. send(req, HttpResponse.BodyHandlers . ofString())
@@ -289,28 +304,35 @@ class TowerFusionToken implements FusionToken {
289
304
/**
290
305
* Request a license token from Platform.
291
306
*
292
- * @param req The LicenseTokenRequest object
307
+ * @param request The LicenseTokenRequest object
293
308
* @return The LicenseTokenResponse object
294
- *
295
- * @throws AbortOperationException if a Platform access token cannot be found
296
- * @throws UnauthorizedException if the access token is invalid
297
- * @throws BadResponseException if the response is not as expected
298
- * @throws IllegalStateException if the request cannot be sent
299
309
*/
300
- private GetLicenseTokenResponse sendRequest (GetLicenseTokenRequest req ) throws AbortOperationException , UnauthorizedException , BadResponseException , IllegalStateException {
310
+ private GetLicenseTokenResponse sendRequest (GetLicenseTokenRequest request ) {
311
+ return sendRequest0(request, 1 )
312
+ }
301
313
302
- final httpReq = makeHttpRequest(req)
314
+ private GetLicenseTokenResponse sendRequest0 (GetLicenseTokenRequest request , int attempt ) {
315
+
316
+ final httpReq = makeHttpRequest(request)
303
317
304
318
try {
305
- final resp = safeHttpSend(httpReq, retryPolicy )
319
+ final resp = safeHttpSend(httpReq)
306
320
307
321
if ( resp. statusCode() == 200 ) {
308
322
final ret = parseLicenseTokenResponse(resp. body())
309
323
return ret
310
324
}
311
325
312
326
if ( resp. statusCode() == 401 ) {
313
- throw new UnauthorizedException (" Unauthorized [401] - Verify you have provided a Seqera Platform valid access token" )
327
+ final shouldRetry = accessToken
328
+ && refreshToken
329
+ && attempt== 1
330
+ && refreshJwtToken0(refreshToken)
331
+ if ( shouldRetry ) {
332
+ return sendRequest0(request, attempt+1 )
333
+ }
334
+ else
335
+ throw new UnauthorizedException (" Unauthorized [401] - Verify you have provided a Seqera Platform valid access token" )
314
336
}
315
337
316
338
throw new BadResponseException (" Invalid response: ${ httpReq.method()} ${ httpReq.uri()} [${ resp.statusCode()} ] ${ resp.body()} " )
@@ -319,4 +341,52 @@ class TowerFusionToken implements FusionToken {
319
341
throw new IllegalStateException (" Unable to send request to '${ httpReq.uri()} ' : ${ e.message} " )
320
342
}
321
343
}
344
+
345
+ protected boolean refreshJwtToken0 (String refresh ) {
346
+ log. debug " Token refresh request >> $refresh "
347
+
348
+ final req = HttpRequest . newBuilder()
349
+ .uri(new URI (" ${ endpoint} /oauth/access_token" ))
350
+ .headers(' Content-Type' ," application/x-www-form-urlencoded" )
351
+ .POST (HttpRequest.BodyPublishers . ofString(" grant_type=refresh_token&refresh_token=${ URLEncoder.encode(refresh, 'UTF-8')} " ))
352
+ .build()
353
+
354
+ final resp = safeHttpSend(req)
355
+ final code = resp. statusCode()
356
+ final body = resp. body()
357
+ log. debug " Refresh cookie response: [${ code} ] ${ body} "
358
+ if ( resp. statusCode() != 200 )
359
+ return false
360
+
361
+ final authCookie = getCookie(' JWT' )
362
+ final refreshCookie = getCookie(' JWT_REFRESH_TOKEN' )
363
+
364
+ // set the new bearer token in the current client session
365
+ if ( authCookie?. value ) {
366
+ log. trace " Updating http client bearer token=$authCookie . value "
367
+ accessToken = authCookie. value
368
+ }
369
+ else {
370
+ log. warn " Missing JWT cookie from refresh token response ~ $authCookie "
371
+ }
372
+
373
+ // set the new refresh token
374
+ if ( refreshCookie?. value ) {
375
+ log. trace " Updating http client refresh token=$refreshCookie . value "
376
+ refreshToken = refreshCookie. value
377
+ }
378
+ else {
379
+ log. warn " Missing JWT_REFRESH_TOKEN cookie from refresh token response ~ $refreshCookie "
380
+ }
381
+
382
+ return true
383
+ }
384
+
385
+ private HttpCookie getCookie (final String cookieName ) {
386
+ for ( HttpCookie it : cookieManager. cookieStore. cookies ) {
387
+ if ( it. name == cookieName )
388
+ return it
389
+ }
390
+ return null
391
+ }
322
392
}
0 commit comments