Skip to content

Commit a70c23f

Browse files
committed
test(auth): address RAB feedback with robust coverage and docs
- Add test for token refresh and RAB lookup sequencing in before_request. - Add failure test to verify blocking RAB lookups are not retried. - Restore and refine test for skipping RAB lookup when URL is None. - Fix swapped url/method arguments in before_request test calls. - Document why OAuth2 credentials skip independent RAB lookups. - Internalize blocking lookup method with leading underscore.
1 parent d2258f4 commit a70c23f

7 files changed

Lines changed: 89 additions & 18 deletions

File tree

packages/google-auth/google/auth/credentials.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,8 +377,13 @@ def _with_regional_access_boundary(self, seed):
377377
creds._rab_manager.set_initial_regional_access_boundary(seed)
378378
return creds
379379

380-
def with_blocking_regional_access_boundary_lookup(self):
380+
def _with_blocking_regional_access_boundary_lookup(self):
381381
"""Returns a copy of these credentials with the blocking lookup mode enabled.
382+
This is intended for internal use only as blocking lookup requires additional
383+
care and consideration. Currently this is unsed by the gcloud CLI and
384+
therefore changes to the contract MUST be backwards compatible (e.g. the
385+
method signature must be unchanged and a copy of the credentials with the
386+
blocking lookup flag set to true must be returned).
382387
383388
Returns:
384389
google.auth.credentials.Credentials: A new credentials instance.

packages/google-auth/google/oauth2/credentials.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ def __setstate__(self, d):
210210
self._rab_manager = d.get("_rab_manager") or (
211211
_regional_access_boundary_utils._RegionalAccessBoundaryManager()
212212
)
213-
self._use_blocking_regional_access_boundary_lookup = False
214213

215214
@property
216215
def refresh_token(self):
@@ -363,6 +362,15 @@ def _metric_header_for_usage(self):
363362
return metrics.CRED_TYPE_USER
364363

365364
def _build_regional_access_boundary_lookup_url(self, request=None):
365+
"""Builds the URL for Regional Access Boundary lookup.
366+
367+
OAuth 2.0 credentials do not support independent Regional Access Boundary
368+
lookup. However, they may support a seeded Regional Access Boundary
369+
provided externally (e.g., from gcloud).
370+
371+
Returns:
372+
None: This credential type does not support RAB lookup.
373+
"""
366374
return None
367375

368376
def _perform_refresh_token(self, request):

packages/google-auth/tests/compute_engine/test_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,7 @@ def test_with_blocking_regional_access_boundary_lookup(self):
455455
creds = self.credentials
456456
assert not creds._rab_manager._use_blocking_regional_access_boundary_lookup
457457

458-
new_creds = creds.with_blocking_regional_access_boundary_lookup()
458+
new_creds = creds._with_blocking_regional_access_boundary_lookup()
459459
assert new_creds._rab_manager._use_blocking_regional_access_boundary_lookup
460460
assert new_creds is not creds
461461

packages/google-auth/tests/oauth2/test__client.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,3 +810,23 @@ def test_lookup_regional_access_boundary_blocking():
810810
mock_request.assert_called_once_with(
811811
method="GET", url=url, headers=headers, timeout=3
812812
)
813+
814+
def test_lookup_regional_access_boundary_blocking_error():
815+
mock_response = mock.create_autospec(transport.Response, instance=True)
816+
mock_response.status = http_client.INTERNAL_SERVER_ERROR
817+
mock_response.data = "this is an error message"
818+
819+
mock_request = mock.create_autospec(transport.Request)
820+
mock_request.return_value = mock_response
821+
822+
url = "http://example.com"
823+
headers = {"Authorization": "Bearer access_token"}
824+
# Even if the error is retryable, blocking=True should prevent retries.
825+
result = _client._lookup_regional_access_boundary(
826+
mock_request, url, headers=headers, blocking=True
827+
)
828+
assert result is None
829+
830+
mock_request.assert_called_once_with(
831+
method="GET", url=url, headers=headers, timeout=3
832+
)

packages/google-auth/tests/oauth2/test_credentials.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def test_with_blocking_regional_access_boundary_lookup(self):
100100
creds = self.make_credentials()
101101
assert not creds._rab_manager._use_blocking_regional_access_boundary_lookup
102102

103-
new_creds = creds.with_blocking_regional_access_boundary_lookup()
103+
new_creds = creds._with_blocking_regional_access_boundary_lookup()
104104
assert new_creds._rab_manager._use_blocking_regional_access_boundary_lookup
105105
assert new_creds is not creds
106106

packages/google-auth/tests/test__regional_access_boundary_utils.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from google.auth import _regional_access_boundary_utils
2323
from google.auth import credentials
2424
from google.auth import environment_vars
25+
from google.oauth2 import credentials as oauth2_credentials
2526

2627

2728
class CredentialsImpl(credentials.CredentialsWithRegionalAccessBoundary):
@@ -227,7 +228,7 @@ def test_with_blocking_regional_access_boundary_lookup(self):
227228
creds = CredentialsImpl()
228229
assert not creds._rab_manager._use_blocking_regional_access_boundary_lookup
229230

230-
new_creds = creds.with_blocking_regional_access_boundary_lookup()
231+
new_creds = creds._with_blocking_regional_access_boundary_lookup()
231232
assert new_creds._rab_manager._use_blocking_regional_access_boundary_lookup
232233

233234
def test_with_regional_access_boundary(self):
@@ -405,6 +406,12 @@ def test_lookup_regional_access_boundary_failure(self, mock_lookup_rab):
405406
assert rab_manager._data.expiry is None
406407
assert rab_manager._data.cooldown_expiry is not None
407408

409+
def test_lookup_regional_access_boundary_null_url(self):
410+
creds = oauth2_credentials.Credentials(token="token")
411+
request = mock.Mock()
412+
result = creds._lookup_regional_access_boundary(request)
413+
assert result is None
414+
408415
def test_credentials_with_regional_access_boundary_initialization(self):
409416
creds = CredentialsImpl()
410417
assert creds._rab_manager._data.encoded_locations is None

packages/google-auth/tests/test_credentials.py

Lines changed: 44 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ def _perform_refresh_token(self, request):
3838
def with_quota_project(self, quota_project_id):
3939
raise NotImplementedError()
4040

41-
def _build_regional_access_boundary_lookup_url(self):
41+
def _build_regional_access_boundary_lookup_url(self, request=None):
4242
# Using self.token here to make the URL dynamic for testing purposes
4343
return "http://mock.url/lookup_for_{}".format(self.token)
4444

@@ -107,7 +107,7 @@ def test_before_request():
107107
headers = {}
108108

109109
# First call should call refresh, setting the token.
110-
credentials.before_request(request, "http://example.com", "GET", headers)
110+
credentials.before_request(request, "GET", "http://example.com", headers)
111111
assert credentials.valid
112112
assert credentials.token == "refreshed-token"
113113
assert headers["authorization"] == "Bearer refreshed-token"
@@ -117,7 +117,7 @@ def test_before_request():
117117
headers = {}
118118

119119
# Second call shouldn't call refresh.
120-
credentials.before_request(request, "http://example.com", "GET", headers)
120+
credentials.before_request(request, "GET", "http://example.com", headers)
121121
assert credentials.valid
122122
assert credentials.token == "refreshed-token"
123123
assert headers["authorization"] == "Bearer refreshed-token"
@@ -137,7 +137,7 @@ def test_before_request_with_regional_access_boundary():
137137
headers = {}
138138

139139
# First call should call refresh, setting the token.
140-
creds.before_request(request, "http://example.com", "GET", headers)
140+
creds.before_request(request, "GET", "http://example.com", headers)
141141
assert creds.valid
142142
assert creds.token == "refreshed-token"
143143
assert headers["authorization"] == "Bearer refreshed-token"
@@ -147,7 +147,7 @@ def test_before_request_with_regional_access_boundary():
147147
headers = {}
148148

149149
# Second call shouldn't call refresh.
150-
creds.before_request(request, "http://example.com", "GET", headers)
150+
creds.before_request(request, "GET", "http://example.com", headers)
151151
assert creds.valid
152152
assert creds.token == "refreshed-token"
153153
assert headers["authorization"] == "Bearer refreshed-token"
@@ -159,7 +159,7 @@ def test_before_request_metrics():
159159
request = "token"
160160
headers = {}
161161

162-
credentials.before_request(request, "http://example.com", "GET", headers)
162+
credentials.before_request(request, "GET", "http://example.com", headers)
163163
assert headers["x-goog-api-client"] == "foo"
164164

165165

@@ -282,7 +282,7 @@ def test_nonblocking_refresh_fresh_credentials():
282282
assert c.token_state == credentials.TokenState.FRESH
283283

284284
c.with_non_blocking_refresh()
285-
c.before_request(request, "http://example.com", "GET", {})
285+
c.before_request(request, "GET", "http://example.com", {})
286286

287287

288288
def test_nonblocking_refresh_invalid_credentials():
@@ -294,7 +294,7 @@ def test_nonblocking_refresh_invalid_credentials():
294294

295295
assert c.token_state == credentials.TokenState.INVALID
296296

297-
c.before_request(request, "http://example.com", "GET", headers)
297+
c.before_request(request, "GET", "http://example.com", headers)
298298
assert c.token_state == credentials.TokenState.FRESH
299299
assert c.valid
300300
assert c.token == "refreshed-token"
@@ -310,7 +310,7 @@ def test_nonblocking_refresh_stale_credentials():
310310
headers = {}
311311

312312
# Invalid credentials MUST require a blocking refresh.
313-
c.before_request(request, "http://example.com", "GET", headers)
313+
c.before_request(request, "GET", "http://example.com", headers)
314314
assert c.token_state == credentials.TokenState.FRESH
315315
assert not c._refresh_worker._worker
316316

@@ -322,7 +322,7 @@ def test_nonblocking_refresh_stale_credentials():
322322

323323
# STALE credentials SHOULD spawn a non-blocking worker
324324
assert c.token_state == credentials.TokenState.STALE
325-
c.before_request(request, "http://example.com", "GET", headers)
325+
c.before_request(request, "GET", "http://example.com", headers)
326326
assert c._refresh_worker._worker is not None
327327

328328
assert c.token_state == credentials.TokenState.FRESH
@@ -340,7 +340,7 @@ def test_nonblocking_refresh_failed_credentials():
340340
headers = {}
341341

342342
# Invalid credentials MUST require a blocking refresh.
343-
c.before_request(request, "http://example.com", "GET", headers)
343+
c.before_request(request, "GET", "http://example.com", headers)
344344
assert c.token_state == credentials.TokenState.FRESH
345345
assert not c._refresh_worker._worker
346346

@@ -354,7 +354,7 @@ def test_nonblocking_refresh_failed_credentials():
354354
assert c.token_state == credentials.TokenState.STALE
355355
c._refresh_worker._worker = mock.MagicMock()
356356
c._refresh_worker._worker._error_info = "Some Error"
357-
c.before_request(request, "http://example.com", "GET", headers)
357+
c.before_request(request, "GET", "http://example.com", headers)
358358
assert c._refresh_worker._worker is not None
359359

360360
assert c.token_state == credentials.TokenState.FRESH
@@ -373,7 +373,7 @@ def test_token_state_no_expiry():
373373
c.expiry = None
374374
assert c.token_state == credentials.TokenState.FRESH
375375

376-
c.before_request(request, "http://example.com", "GET", {})
376+
c.before_request(request, "GET", "http://example.com", {})
377377

378378

379379
def test_credentials_with_trust_boundary_bridge():
@@ -389,3 +389,34 @@ def _build_trust_boundary_lookup_url(self):
389389
# Verify that calling the new method delegates to the old method
390390
with pytest.warns(DeprecationWarning):
391391
assert creds._build_regional_access_boundary_lookup_url() == "http://legacy.url"
392+
393+
def test_before_request_triggers_rab_refresh():
394+
with mock.patch(
395+
"google.auth._regional_access_boundary_utils.is_regional_access_boundary_enabled",
396+
return_value=True,
397+
):
398+
with mock.patch("google.oauth2._client._lookup_regional_access_boundary") as lookup:
399+
lookup.return_value = {"encodedLocations": "0xA30"}
400+
401+
creds = CredentialsImpl()
402+
creds = creds._with_blocking_regional_access_boundary_lookup()
403+
404+
request = mock.Mock()
405+
headers = {}
406+
407+
# Initial state: no token
408+
assert creds.token is None
409+
410+
# before_request should trigger token refresh and THEN RAB refresh.
411+
# We verify this by checking that the RAB lookup was called with
412+
# the URL containing the refreshed token.
413+
creds.before_request(request, "GET", "http://example.com", headers)
414+
415+
assert creds.token == "refreshed-token"
416+
assert headers["authorization"] == "Bearer refreshed-token"
417+
assert headers["x-allowed-locations"] == "0xA30"
418+
419+
# Verify lookup was called with the refreshed token's URL
420+
lookup.assert_called_once()
421+
args, kwargs = lookup.call_args
422+
assert args[1] == "http://mock.url/lookup_for_refreshed-token"

0 commit comments

Comments
 (0)