Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/google/adk/auth/auth_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ def get_credential_key(self):
auth_credential.oauth2.refresh_token = None
auth_credential.oauth2.expires_at = None
auth_credential.oauth2.expires_in = None
auth_credential.oauth2.redirect_uri = None
credential_name = (
f"{auth_credential.auth_type.value}_{_stable_model_digest(auth_credential)}"
if auth_credential
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def _get_legacy_credential_key(
auth_credential.oauth2.refresh_token = None
auth_credential.oauth2.expires_at = None
auth_credential.oauth2.expires_in = None
auth_credential.oauth2.redirect_uri = None
scheme_name = (
f"{auth_scheme.type_.name}_{self._legacy_stable_digest(auth_scheme.model_dump_json())}"
if auth_scheme
Expand Down Expand Up @@ -99,6 +100,7 @@ def get_credential_key(
auth_credential.oauth2.refresh_token = None
auth_credential.oauth2.expires_at = None
auth_credential.oauth2.expires_in = None
auth_credential.oauth2.redirect_uri = None
scheme_name = (
f"{auth_scheme.type_.name}_{_stable_model_digest(auth_scheme)}"
if auth_scheme
Expand Down
38 changes: 38 additions & 0 deletions tests/unittests/auth/test_auth_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,41 @@ def test_credential_key_with_custom_auth_scheme():
key = custom_config.credential_key
assert key.startswith("adk_mock_custom_type_")
assert len(key) > len("adk_mock_custom_type_")


def test_credential_key_is_stable_across_redirect_uri(oauth2_auth_scheme):
"""AuthConfig.credential_key should be invariant under redirect_uri changes.

redirect_uri is deployment configuration (which callback URL the auth
server should redirect to), not part of the credential identity. Two
AuthConfig instances built from credentials that share the same client_id,
client_secret, and scopes but differ only in redirect_uri should produce
the same credential_key.
"""
credential_local = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id="client",
client_secret="secret",
redirect_uri="http://localhost:8001/oauth2callback",
),
)
credential_deployed = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id="client",
client_secret="secret",
redirect_uri="https://deployed.example.com/oauth2callback",
),
)

config_local = AuthConfig(
auth_scheme=oauth2_auth_scheme,
raw_auth_credential=credential_local,
)
config_deployed = AuthConfig(
auth_scheme=oauth2_auth_scheme,
raw_auth_credential=credential_deployed,
)

assert config_local.credential_key == config_deployed.credential_key
Original file line number Diff line number Diff line change
Expand Up @@ -353,3 +353,65 @@ async def test_refreshed_credential_is_persisted_to_store(
assert persisted is not None
assert persisted.oauth2.access_token == 'new_access_token'
assert persisted.oauth2.refresh_token == 'new_refresh_token'


def test_credential_key_is_stable_across_redirect_uri():
"""get_credential_key should be invariant under redirect_uri changes.

redirect_uri is deployment configuration (which callback URL the auth
server should redirect to), not part of the credential identity. Two
AuthCredential instances that share the same client_id, client_secret,
and scopes but differ only in redirect_uri should produce the same key.
"""
scheme, _ = get_mock_openid_scheme_credential()
credential_local = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id='client',
client_secret='secret',
redirect_uri='http://localhost:8001/oauth2callback',
),
)
credential_deployed = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id='client',
client_secret='secret',
redirect_uri='https://deployed.example.com/oauth2callback',
),
)
store = ToolContextCredentialStore(tool_context=create_mock_tool_context())

assert store.get_credential_key(
scheme, credential_local
) == store.get_credential_key(scheme, credential_deployed)


def test_legacy_credential_key_is_stable_across_redirect_uri():
"""_get_legacy_credential_key should be invariant under redirect_uri changes.

The same redirect_uri-strip behavior must apply to the legacy key path so
that already-stored credentials remain findable after the fix.
"""
scheme, _ = get_mock_openid_scheme_credential()
credential_local = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id='client',
client_secret='secret',
redirect_uri='http://localhost:8001/oauth2callback',
),
)
credential_deployed = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id='client',
client_secret='secret',
redirect_uri='https://deployed.example.com/oauth2callback',
),
)
store = ToolContextCredentialStore(tool_context=create_mock_tool_context())

assert store._get_legacy_credential_key(
scheme, credential_local
) == store._get_legacy_credential_key(scheme, credential_deployed)
Loading