mirror of
https://github.com/encounter/adk-python.git
synced 2026-07-09 18:19:28 -07:00
feat: add token_endpoint_auth_method support to OAuth2 credentials
Merge https://github.com/google/adk-python/pull/2870 ## Summary Add `token_endpoint_auth_method` field to OAuth2Auth class to allow configuring OAuth2 token endpoint authentication methods. This enables users to specify how the client should authenticate with the authorization server's token endpoint. • Add `token_endpoint_auth_method` field to `OAuth2Auth` with default value `"client_secret_basic"` • Update `create_oauth2_session()` to pass the authentication method to `OAuth2Session` • Maintain backward compatibility with existing OAuth2 configurations ## Unit Tests Added unit test coverage with 3 new test methods: 1. `test_create_oauth2_session_with_token_endpoint_auth_method()` - Tests explicit auth method setting (`client_secret_post`) 2. `test_create_oauth2_session_with_default_token_endpoint_auth_method()` - Tests default behavior (`client_secret_basic`) 3. `test_create_oauth2_session_oauth2_scheme_with_token_endpoint_auth_method()` - Tests with OAuth2 scheme using `client_secret_jwt` **Test Results:** ✅ 16/16 OAuth2 credential utility tests passed ✅ 240/240 auth module tests passed (no regressions) ✅ Tests cover both GOOGLE_AI and VERTEX variants ✅ Pylint score: 9.41/10 ## Changes Made **src/google/adk/auth/auth_credential.py** - Added `token_endpoint_auth_method: Optional[str] = "client_secret_basic"` to `OAuth2Auth` class **src/google/adk/auth/oauth2_credential_util.py** - Updated `create_oauth2_session()` to pass `token_endpoint_auth_method` parameter to `OAuth2Session` **tests/unittests/auth/test_oauth2_credential_util.py** - Added 3 comprehensive test methods covering different authentication scenarios ## Backward Compatibility ✅ **Non-breaking change** - All existing OAuth2 configurations continue to work unchanged with the default `client_secret_basic` authentication method. ## Supported Authentication Methods - `client_secret_basic` (default) - Client credentials in Authorization header - `client_secret_post` - Client credentials in request body - `client_secret_jwt` - JWT with client secret - `private_key_jwt` - JWT with private key Co-authored-by: Xiang (Sean) Zhou <seanzhougoogle@google.com> COPYBARA_INTEGRATE_REVIEW=https://github.com/google/adk-python/pull/2870 from sully90:feat/oauth2-token-endpoint-auth-method 04fe8244598f96b4e3366f0fc79382628382e9c2 PiperOrigin-RevId: 843739984
This commit is contained in:
committed by
Copybara-Service
parent
29c1115959
commit
8782a69503
@@ -18,6 +18,7 @@ from enum import Enum
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Literal
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import alias_generators
|
||||
@@ -80,6 +81,14 @@ class OAuth2Auth(BaseModelWithConfig):
|
||||
expires_at: Optional[int] = None
|
||||
expires_in: Optional[int] = None
|
||||
audience: Optional[str] = None
|
||||
token_endpoint_auth_method: Optional[
|
||||
Literal[
|
||||
"client_secret_basic",
|
||||
"client_secret_post",
|
||||
"client_secret_jwt",
|
||||
"private_key_jwt",
|
||||
]
|
||||
] = "client_secret_basic"
|
||||
|
||||
|
||||
class ServiceAccountCredential(BaseModelWithConfig):
|
||||
|
||||
@@ -91,6 +91,7 @@ def create_oauth2_session(
|
||||
scope=" ".join(scopes),
|
||||
redirect_uri=auth_credential.oauth2.redirect_uri,
|
||||
state=auth_credential.oauth2.state,
|
||||
token_endpoint_auth_method=auth_credential.oauth2.token_endpoint_auth_method,
|
||||
),
|
||||
token_endpoint,
|
||||
)
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
import time
|
||||
from typing import Optional
|
||||
from unittest.mock import Mock
|
||||
|
||||
from authlib.oauth2.rfc6749 import OAuth2Token
|
||||
@@ -25,6 +26,39 @@ from google.adk.auth.auth_credential import OAuth2Auth
|
||||
from google.adk.auth.auth_schemes import OpenIdConnectWithConfig
|
||||
from google.adk.auth.oauth2_credential_util import create_oauth2_session
|
||||
from google.adk.auth.oauth2_credential_util import update_credential_with_tokens
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def openid_connect_scheme() -> OpenIdConnectWithConfig:
|
||||
"""Fixture providing a standard OpenIdConnectWithConfig scheme."""
|
||||
return OpenIdConnectWithConfig(
|
||||
type_="openIdConnect",
|
||||
openId_connect_url="https://example.com/.well-known/openid_configuration",
|
||||
authorization_endpoint="https://example.com/auth",
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid", "profile"],
|
||||
)
|
||||
|
||||
|
||||
def create_oauth2_auth_credential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
token_endpoint_auth_method: Optional[str] = None,
|
||||
):
|
||||
"""Helper function to create OAuth2Auth credential with optional token_endpoint_auth_method."""
|
||||
oauth2_auth = OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
redirect_uri="https://example.com/callback",
|
||||
state="test_state",
|
||||
)
|
||||
if token_endpoint_auth_method is not None:
|
||||
oauth2_auth.token_endpoint_auth_method = token_endpoint_auth_method
|
||||
|
||||
return AuthCredential(
|
||||
auth_type=auth_type,
|
||||
oauth2=oauth2_auth,
|
||||
)
|
||||
|
||||
|
||||
class TestOAuth2CredentialUtil:
|
||||
@@ -41,14 +75,9 @@ class TestOAuth2CredentialUtil:
|
||||
token_endpoint="https://example.com/token",
|
||||
scopes=["openid", "profile"],
|
||||
)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
redirect_uri="https://example.com/callback",
|
||||
state="test_state",
|
||||
),
|
||||
credential = create_oauth2_auth_credential(
|
||||
auth_type=AuthCredentialTypes.OAUTH2,
|
||||
token_endpoint_auth_method="client_secret_jwt",
|
||||
)
|
||||
|
||||
client, token_endpoint = create_oauth2_session(scheme, credential)
|
||||
@@ -122,6 +151,62 @@ class TestOAuth2CredentialUtil:
|
||||
assert client is None
|
||||
assert token_endpoint is None
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"token_endpoint_auth_method, expected_auth_method",
|
||||
[
|
||||
("client_secret_post", "client_secret_post"),
|
||||
(None, "client_secret_basic"),
|
||||
],
|
||||
)
|
||||
def test_create_oauth2_session_with_token_endpoint_auth_method(
|
||||
self,
|
||||
openid_connect_scheme,
|
||||
token_endpoint_auth_method,
|
||||
expected_auth_method,
|
||||
):
|
||||
"""Test create_oauth2_session with various token_endpoint_auth_method settings."""
|
||||
credential = create_oauth2_auth_credential(
|
||||
token_endpoint_auth_method=token_endpoint_auth_method
|
||||
)
|
||||
|
||||
client, token_endpoint = create_oauth2_session(
|
||||
openid_connect_scheme, credential
|
||||
)
|
||||
|
||||
assert client is not None
|
||||
assert token_endpoint == "https://example.com/token"
|
||||
assert client.client_id == "test_client_id"
|
||||
assert client.client_secret == "test_client_secret"
|
||||
assert client.token_endpoint_auth_method == expected_auth_method
|
||||
|
||||
def test_create_oauth2_session_oauth2_scheme_with_token_endpoint_auth_method(
|
||||
self,
|
||||
):
|
||||
"""Test create_oauth2_session with OAuth2 scheme and token_endpoint_auth_method."""
|
||||
flows = OAuthFlows(
|
||||
authorizationCode=OAuthFlowAuthorizationCode(
|
||||
authorizationUrl="https://example.com/auth",
|
||||
tokenUrl="https://example.com/token",
|
||||
scopes={"read": "Read access", "write": "Write access"},
|
||||
)
|
||||
)
|
||||
scheme = OAuth2(type_="oauth2", flows=flows)
|
||||
credential = AuthCredential(
|
||||
auth_type=AuthCredentialTypes.OAUTH2,
|
||||
oauth2=OAuth2Auth(
|
||||
client_id="test_client_id",
|
||||
client_secret="test_client_secret",
|
||||
redirect_uri="https://example.com/callback",
|
||||
token_endpoint_auth_method="client_secret_jwt",
|
||||
),
|
||||
)
|
||||
|
||||
client, token_endpoint = create_oauth2_session(scheme, credential)
|
||||
|
||||
assert client is not None
|
||||
assert token_endpoint == "https://example.com/token"
|
||||
assert client.token_endpoint_auth_method == "client_secret_jwt"
|
||||
|
||||
def test_update_credential_with_tokens(self):
|
||||
"""Test update_credential_with_tokens function."""
|
||||
credential = AuthCredential(
|
||||
|
||||
Reference in New Issue
Block a user