@@ -243,4 +243,254 @@ describe("access token", function ()
243243 end )
244244 end )
245245
246+ -- Tests for custom URL configuration
247+ describe (" custom URL configuration" , function ()
248+
249+ it (" should use custom oauth_token_url for Service Account authentication" , function ()
250+ local custom_oauth_url = " https://private.googleapis.com/oauth2/v4/token"
251+ local custom_responses = {
252+ [custom_oauth_url ] = {
253+ status = 200 ,
254+ body = [[ {"access_token": "test_custom_oauth_token", "expires_in": 3600}]] ,
255+ },
256+ [" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" ] = function ()
257+ return nil , " connection refused"
258+ end
259+ }
260+
261+ with_http_mock (custom_responses , function (temp_access_token )
262+ local gcpToken = temp_access_token (nil , {
263+ auth_method_order = " adc" ,
264+ oauth_token_url = custom_oauth_url
265+ })
266+
267+ assert .same (gcpToken .token , " test_custom_oauth_token" )
268+ assert .same (gcpToken .authMethod , " SA" )
269+ assert .same (gcpToken .oauthTokenUrl , custom_oauth_url )
270+ assert .is_number (gcpToken .expireTime )
271+ assert .is_false (gcpToken :needsRefresh ())
272+ end )
273+ end )
274+
275+ it (" should use custom metadata_url for Workload Identity authentication" , function ()
276+ local custom_metadata_url = " http://custom-metadata.internal/token"
277+ local custom_responses = {
278+ [" https://www.googleapis.com/oauth2/v4/token" ] = {
279+ status = 400 ,
280+ body = [[ {"error": "invalid_grant"}]] ,
281+ },
282+ [custom_metadata_url ] = {
283+ status = 200 ,
284+ body = [[ {"access_token": "test_custom_wi_token", "expires_in": 3600}]] ,
285+ }
286+ }
287+
288+ with_http_mock (custom_responses , function (temp_access_token )
289+ local gcpToken = temp_access_token (nil , {
290+ auth_method_order = " legacy" ,
291+ metadata_url = custom_metadata_url
292+ })
293+
294+ assert .same (gcpToken .token , " test_custom_wi_token" )
295+ assert .same (gcpToken .authMethod , " WI" )
296+ assert .same (gcpToken .metadataUrl , custom_metadata_url )
297+ assert .is_number (gcpToken .expireTime )
298+ assert .is_false (gcpToken :needsRefresh ())
299+ end )
300+ end )
301+
302+ it (" should use both custom oauth_token_url and metadata_url" , function ()
303+ local custom_oauth_url = " https://restricted.googleapis.com/oauth2/v4/token"
304+ local custom_metadata_url = " http://private-metadata.internal/token"
305+ local custom_responses = {
306+ [custom_oauth_url ] = {
307+ status = 200 ,
308+ body = [[ {"access_token": "test_custom_both_token", "expires_in": 3600}]] ,
309+ },
310+ [custom_metadata_url ] = {
311+ status = 200 ,
312+ body = [[ {"access_token": "test_custom_wi_both_token", "expires_in": 3600}]] ,
313+ }
314+ }
315+
316+ with_http_mock (custom_responses , function (temp_access_token )
317+ -- Test with ADC (SA first)
318+ local gcpTokenSA = temp_access_token (nil , {
319+ auth_method_order = " adc" ,
320+ oauth_token_url = custom_oauth_url ,
321+ metadata_url = custom_metadata_url
322+ })
323+
324+ assert .same (gcpTokenSA .token , " test_custom_both_token" )
325+ assert .same (gcpTokenSA .authMethod , " SA" )
326+ assert .same (gcpTokenSA .oauthTokenUrl , custom_oauth_url )
327+ assert .same (gcpTokenSA .metadataUrl , custom_metadata_url )
328+ end )
329+
330+ with_http_mock (custom_responses , function (temp_access_token )
331+ -- Test with legacy (WI first)
332+ local gcpTokenWI = temp_access_token (nil , {
333+ auth_method_order = " legacy" ,
334+ oauth_token_url = custom_oauth_url ,
335+ metadata_url = custom_metadata_url
336+ })
337+
338+ assert .same (gcpTokenWI .token , " test_custom_wi_both_token" )
339+ assert .same (gcpTokenWI .authMethod , " WI" )
340+ assert .same (gcpTokenWI .oauthTokenUrl , custom_oauth_url )
341+ assert .same (gcpTokenWI .metadataUrl , custom_metadata_url )
342+ end )
343+ end )
344+
345+ it (" should use custom oauth_token_url during SA token refresh" , function ()
346+ local custom_oauth_url = " https://private.googleapis.com/oauth2/v4/token"
347+ local refresh_counter = 0
348+ local custom_responses = {
349+ [custom_oauth_url ] = function ()
350+ refresh_counter = refresh_counter + 1
351+ return {
352+ status = 200 ,
353+ body = string.format ([[ {"access_token": "test_refresh_token_%d", "expires_in": 3600}]] , refresh_counter ),
354+ }
355+ end ,
356+ [" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" ] = function ()
357+ return nil , " connection refused"
358+ end
359+ }
360+
361+ with_http_mock (custom_responses , function (temp_access_token )
362+ local gcpToken = temp_access_token (nil , {
363+ auth_method_order = " adc" ,
364+ oauth_token_url = custom_oauth_url
365+ })
366+
367+ -- First token
368+ assert .same (gcpToken .token , " test_refresh_token_1" )
369+ assert .same (gcpToken .authMethod , " SA" )
370+ assert .equals (1 , refresh_counter )
371+
372+ -- Force refresh
373+ gcpToken .expireTime = ngx .now () - 100
374+ assert .is_true (gcpToken :needsRefresh ())
375+
376+ -- Access token property to trigger refresh
377+ local refreshed_token = gcpToken .token
378+ assert .same (refreshed_token , " test_refresh_token_2" )
379+ assert .equals (2 , refresh_counter )
380+ assert .is_false (gcpToken :needsRefresh ())
381+ end )
382+ end )
383+
384+ it (" should use custom metadata_url during WI token refresh" , function ()
385+ local custom_metadata_url = " http://custom-metadata.internal/token"
386+ local refresh_counter = 0
387+ local custom_responses = {
388+ [" https://www.googleapis.com/oauth2/v4/token" ] = {
389+ status = 400 ,
390+ body = [[ {"error": "invalid_grant"}]] ,
391+ },
392+ [custom_metadata_url ] = function ()
393+ refresh_counter = refresh_counter + 1
394+ return {
395+ status = 200 ,
396+ body = string.format ([[ {"access_token": "test_wi_refresh_token_%d", "expires_in": 3600}]] , refresh_counter ),
397+ }
398+ end
399+ }
400+
401+ with_http_mock (custom_responses , function (temp_access_token )
402+ local gcpToken = temp_access_token (nil , {
403+ auth_method_order = " legacy" ,
404+ metadata_url = custom_metadata_url
405+ })
406+
407+ -- First token
408+ assert .same (gcpToken .token , " test_wi_refresh_token_1" )
409+ assert .same (gcpToken .authMethod , " WI" )
410+ assert .equals (1 , refresh_counter )
411+
412+ -- Force refresh
413+ gcpToken .expireTime = ngx .now () - 100
414+ assert .is_true (gcpToken :needsRefresh ())
415+
416+ -- Access token property to trigger refresh
417+ local refreshed_token = gcpToken .token
418+ assert .same (refreshed_token , " test_wi_refresh_token_2" )
419+ assert .equals (2 , refresh_counter )
420+ assert .is_false (gcpToken :needsRefresh ())
421+ end )
422+ end )
423+
424+ it (" should maintain backward compatibility with default URLs when no custom URLs provided" , function ()
425+ -- Test that when no custom URLs are provided, default URLs are used
426+ local gcpToken = access_token (nil , { auth_method_order = " legacy" })
427+
428+ assert .same (gcpToken .token , " test_wi" )
429+ assert .same (gcpToken .oauthTokenUrl , " https://www.googleapis.com/oauth2/v4/token" )
430+ assert .same (gcpToken .metadataUrl , " http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" )
431+ assert .is_number (gcpToken .expireTime )
432+ assert .is_false (gcpToken :needsRefresh ())
433+ end )
434+
435+ it (" should use default URL when custom URL is nil" , function ()
436+ local gcpToken = access_token (nil , {
437+ auth_method_order = " legacy" ,
438+ oauth_token_url = nil , -- explicitly set to nil
439+ metadata_url = nil -- explicitly set to nil
440+ })
441+
442+ assert .same (gcpToken .token , " test_wi" )
443+ assert .same (gcpToken .oauthTokenUrl , " https://www.googleapis.com/oauth2/v4/token" )
444+ assert .same (gcpToken .metadataUrl , " http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token" )
445+ end )
446+
447+ it (" should use custom oauth_token_url in JWT aud field" , function ()
448+ local custom_oauth_url = " https://custom.googleapis.com/oauth2/v4/token"
449+ local captured_request_body = nil
450+
451+ -- We need to modify the HTTP mock to capture request details
452+ local original_http = package.loaded [" resty.luasocket.http" ]
453+ package.loaded [" resty.luasocket.http" ] = {
454+ new = function ()
455+ return {
456+ close = function () return true end ,
457+ request_uri = function (self , url , opts )
458+ if url == custom_oauth_url then
459+ captured_request_body = opts .body
460+ return {
461+ status = 200 ,
462+ body = [[ {"access_token": "test_custom_aud_token", "expires_in": 3600}]] ,
463+ }
464+ else
465+ return nil , " connection refused"
466+ end
467+ end ,
468+ }
469+ end ,
470+ }
471+
472+ package.loaded [" resty.gcp.request.credentials.accesstoken" ] = nil
473+ local temp_access_token = require " resty.gcp.request.credentials.accesstoken"
474+
475+ local gcpToken = temp_access_token (nil , {
476+ auth_method_order = " adc" ,
477+ oauth_token_url = custom_oauth_url
478+ })
479+
480+ assert .same (gcpToken .token , " test_custom_aud_token" )
481+ assert .is_not_nil (captured_request_body )
482+
483+ -- Verify the JWT contains the custom URL (it's in the assertion field of the request)
484+ local cjson = require (" cjson.safe" ).new ()
485+ local request_data = cjson .decode (captured_request_body )
486+ assert .is_not_nil (request_data )
487+ assert .is_not_nil (request_data .assertion )
488+
489+ -- Restore original state
490+ package.loaded [" resty.luasocket.http" ] = original_http
491+ package.loaded [" resty.gcp.request.credentials.accesstoken" ] = nil
492+ end )
493+
494+ end )
495+
246496end )
0 commit comments