Skip to content

Commit 8018835

Browse files
committed
feat(ve_identity): Add Identity Service integration with OAuth2, API Key, and Workload authentication
This commit introduces a comprehensive integration with Identity Service, enabling ADK agents to securely manage authentication and credentials. ## Key Features ### 1. Unified Authentication Framework - **Three Authentication Types**: - OAuth2 (M2M and USER_FEDERATION flows) - API Key authentication - Workload access token authentication - **Flexible Configuration**: Simple factory functions (`api_key_auth()`, `oauth2_auth()`, `workload_auth()`) for easy setup ### 2. Tool Integration - **VeIdentityFunctionTool**: Funtion tool wrapper with built-in Identity authentication - **VeIdentityMcpTool**: MCP tool wrapper with built-in Identity authentication - **VeIdentityMcpToolset**: Complete MCP toolset management with automatic credential handling ### 3. Authentication Processing - **AuthRequestProcessor**: Handles OAuth2 flows in agent conversations with support for: - Custom OAuth2 auth pollers - Callback URL handling - Token polling with configurable timeout - Mock auth poller for testing - **Auth Mixins**: Reusable authentication logic (`VeIdentityAuthMixin`, `ApiKeyAuthMixin`, `OAuth2AuthMixin`, `WorkloadAuthMixin`) to avoid code duplication ### 4. Token Management - **WorkloadTokenManager**: Manages workload access tokens with: - Automatic caching in session state - Token expiration handling - Support for JWT, user ID, and workload-only authentication modes - Automatic token refresh ### 5. Identity Client - **IdentityClient**: Low-level async client for VolcEngine Identity Service API with: - OAuth2 credential provider management - API key credential provider management - Workload token retrieval - OAuth2 token and API key fetching - Dynamic Client Registration (DCR) support ### 6. Data Models - **OAuth2TokenResponse**: Structured response for OAuth2 token requests - **WorkloadToken**: Workload token with expiration tracking - **OAuth2AuthPoller**: Abstract base for custom token polling implementations - **DCR Models**: Support for RFC 7591 Dynamic Client Registration Protocol - **Authorization Server Metadata**: RFC 8414 compliant metadata handling ### 7. Utility Functions - **is_pending_auth_event()**: Detect pending authentication requests in ADK events - **get_function_call_id()**: Extract function call IDs from auth events - **get_function_call_auth_config()**: Extract auth configuration from events - **generate_headers()**: Convert credentials to HTTP authentication headers
1 parent 37c3702 commit 8018835

File tree

11 files changed

+3166
-0
lines changed

11 files changed

+3166
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Identity integration for ADK.
16+
17+
This module provides integration with VolcEngine Identity Service for managing
18+
authentication and credentials in ADK agents.
19+
20+
Main components:
21+
- IdentityClient: Low-level client for identity service API calls
22+
- WorkloadTokenManager: Manages workload access tokens with caching
23+
- AuthRequestProcessor: Handles OAuth2 flows in agent conversations
24+
25+
Example usage:
26+
from veadk.integrations.ve_identity import VeIdentityFunctionTool
27+
28+
@VeIdentityFunctionTool(
29+
provider_name="github",
30+
scopes=["repo", "user"],
31+
auth_flow="USER_FEDERATION",
32+
)
33+
async def get_github_repos(access_token: str):
34+
# Tool implementation
35+
pass
36+
"""
37+
38+
from .auth_processor import (
39+
AuthRequestConfig,
40+
AuthRequestProcessor,
41+
_NoOpAuthProcessor,
42+
MockOauth2AuthPoller,
43+
get_function_call_auth_config,
44+
get_function_call_id,
45+
is_pending_auth_event,
46+
)
47+
48+
# New unified tools
49+
from .auth_config import (
50+
api_key_auth,
51+
oauth2_auth,
52+
workload_auth,
53+
ApiKeyAuthConfig,
54+
OAuth2AuthConfig,
55+
WorkloadAuthConfig,
56+
VeIdentityAuthConfig,
57+
)
58+
from .function_tool import VeIdentityFunctionTool
59+
from .mcp_tool import VeIdentityMcpTool
60+
from .mcp_toolset import VeIdentityMcpToolset
61+
from .identity_client import IdentityClient
62+
from .models import (
63+
OAuth2TokenResponse,
64+
OAuth2AuthPoller,
65+
WorkloadToken,
66+
)
67+
from .token_manager import WorkloadTokenManager, get_workload_token
68+
69+
__all__ = [
70+
# Client
71+
"IdentityClient",
72+
# Token management
73+
"WorkloadTokenManager",
74+
"get_workload_token",
75+
"VeIdentityFunctionTool",
76+
"VeIdentityMcpTool",
77+
"VeIdentityMcpToolset",
78+
# Auth configurations
79+
"api_key_auth",
80+
"oauth2_auth",
81+
"workload_auth",
82+
"ApiKeyAuthConfig",
83+
"OAuth2AuthConfig",
84+
"WorkloadAuthConfig",
85+
"VeIdentityAuthConfig",
86+
# Auth processor
87+
"AuthRequestProcessor",
88+
"_NoOpAuthProcessor",
89+
"AuthRequestConfig",
90+
"is_pending_auth_event",
91+
"get_function_call_id",
92+
"get_function_call_auth_config",
93+
# Models
94+
"OAuth2TokenResponse",
95+
"WorkloadToken",
96+
"OAuth2AuthPoller",
97+
"MockOauth2AuthPoller",
98+
]
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""
16+
Authentication configuration classes for Identity integration.
17+
"""
18+
19+
from __future__ import annotations
20+
21+
from abc import ABC, abstractmethod
22+
from typing import Any, Callable, List, Literal, Optional, Union
23+
24+
from pydantic import BaseModel, model_validator, field_validator
25+
26+
from .models import OAuth2AuthPoller
27+
from .identity_client import IdentityClient
28+
29+
30+
class AuthConfig(BaseModel, ABC):
31+
"""Base authentication configuration."""
32+
33+
model_config = {"arbitrary_types_allowed": True}
34+
35+
provider_name: str
36+
identity_client: Optional[IdentityClient] = None
37+
region: str = "cn-beijing"
38+
39+
@field_validator("provider_name")
40+
@classmethod
41+
def validate_provider_name_not_empty(cls, v: str) -> str:
42+
"""Validate that provider_name is not empty."""
43+
if not v or not v.strip():
44+
raise ValueError("provider_name cannot be empty")
45+
return v.strip()
46+
47+
@property
48+
@abstractmethod
49+
def auth_type(self) -> str:
50+
"""Return the authentication type identifier."""
51+
pass
52+
53+
54+
class ApiKeyAuthConfig(AuthConfig):
55+
"""API Key authentication configuration."""
56+
57+
@property
58+
def auth_type(self) -> str:
59+
return "api_key"
60+
61+
62+
class OAuth2AuthConfig(AuthConfig):
63+
"""OAuth2 authentication configuration."""
64+
65+
# Required fields
66+
scopes: List[str]
67+
auth_flow: Literal["M2M", "USER_FEDERATION"]
68+
# Optional fields
69+
callback_url: Optional[str] = None
70+
force_authentication: bool = False
71+
response_for_auth_required: Optional[Union[dict, str]] = None
72+
on_auth_url: Optional[Callable[[str], Any]] = None
73+
oauth2_auth_poller: Optional[Callable[[Any], OAuth2AuthPoller]] = None
74+
75+
@field_validator("scopes")
76+
@classmethod
77+
def validate_scopes_not_empty(cls, v: List[str]) -> List[str]:
78+
"""Validate that scopes list is not empty and contains valid scope strings."""
79+
if not v:
80+
raise ValueError("scopes cannot be empty")
81+
82+
# Validate each scope is not empty
83+
for scope in v:
84+
if not scope or not scope.strip():
85+
raise ValueError("scope values cannot be empty")
86+
87+
# Remove duplicates while preserving order
88+
seen = set()
89+
unique_scopes = []
90+
for scope in v:
91+
scope = scope.strip()
92+
if scope not in seen:
93+
seen.add(scope)
94+
unique_scopes.append(scope)
95+
96+
return unique_scopes
97+
98+
@field_validator("callback_url")
99+
@classmethod
100+
def validate_callback_url(cls, v: Optional[str]) -> Optional[str]:
101+
"""Validate callback URL format if provided."""
102+
if v is not None:
103+
v = v.strip()
104+
if not v:
105+
return None
106+
# Basic URL validation
107+
if not (v.startswith("http://") or v.startswith("https://")):
108+
raise ValueError("callback_url must be a valid HTTP/HTTPS URL")
109+
return v
110+
111+
@model_validator(mode="after")
112+
def _validate_required_fields(self):
113+
"""Validate required fields."""
114+
if not self.scopes:
115+
raise ValueError("scopes is required for OAuth2AuthConfig")
116+
if not self.auth_flow:
117+
raise ValueError("auth_flow is required for OAuth2AuthConfig")
118+
return self
119+
120+
@property
121+
def auth_type(self) -> str:
122+
return "oauth2"
123+
124+
class WorkloadAuthConfig(AuthConfig):
125+
"""Workload Access Token authentication configuration."""
126+
127+
@property
128+
def auth_type(self) -> str:
129+
return "workload"
130+
131+
132+
133+
# Type alias for all auth configs
134+
VeIdentityAuthConfig = Union[ApiKeyAuthConfig, OAuth2AuthConfig, WorkloadAuthConfig]
135+
136+
137+
# Convenience factory functions
138+
def api_key_auth(
139+
provider_name: str,
140+
identity_client: Optional[IdentityClient] = None,
141+
region: str = "cn-beijing",
142+
) -> ApiKeyAuthConfig:
143+
"""Create an API key authentication configuration."""
144+
return ApiKeyAuthConfig(
145+
provider_name=provider_name, identity_client=identity_client, region=region
146+
)
147+
148+
def workload_auth(
149+
provider_name: str,
150+
identity_client: Optional[IdentityClient] = None,
151+
region: str = "cn-beijing",
152+
) -> WorkloadAuthConfig:
153+
"""Create a workload authentication configuration."""
154+
return WorkloadAuthConfig(
155+
provider_name=provider_name, identity_client=identity_client, region=region
156+
)
157+
158+
def oauth2_auth(
159+
provider_name: str,
160+
scopes: List[str],
161+
auth_flow: Literal["M2M", "USER_FEDERATION"],
162+
callback_url: Optional[str] = None,
163+
force_authentication: bool = False,
164+
response_for_auth_required: Optional[Union[dict, str]] = None,
165+
on_auth_url: Optional[Callable[[str], Any]] = None,
166+
oauth2_auth_poller: Optional[Callable[[Any], OAuth2AuthPoller]] = None,
167+
identity_client: Optional[IdentityClient] = None,
168+
region: str = "cn-beijing",
169+
) -> OAuth2AuthConfig:
170+
"""Create an OAuth2 authentication configuration."""
171+
return OAuth2AuthConfig(
172+
provider_name=provider_name,
173+
scopes=scopes,
174+
auth_flow=auth_flow,
175+
callback_url=callback_url,
176+
force_authentication=force_authentication,
177+
response_for_auth_required=response_for_auth_required,
178+
on_auth_url=on_auth_url,
179+
oauth2_auth_poller=oauth2_auth_poller,
180+
identity_client=identity_client,
181+
region=region,
182+
)

0 commit comments

Comments
 (0)