Skip to content

Commit 9bed908

Browse files
committed
chore(ve_identity): Make OAuth2 flow and scopes parameters optional
Updated OAuth2-related classes and functions to make 'auth_flow' and 'scopes' parameters optional, allowing control plane defaults to be used if not provided.
1 parent f0d5941 commit 9bed908

File tree

12 files changed

+137
-152
lines changed

12 files changed

+137
-152
lines changed

docs/content/91.auth/2.api-key-outbound.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,6 @@ toolset = VeIdentityMcpToolset(
5353
)
5454
```
5555

56-
## 环境变量配置
57-
58-
```bash
59-
export VOLCENGINE_ACCESS_KEY="your-access-key-id"
60-
export VOLCENGINE_SECRET_KEY="your-secret-access-key"
61-
export VOLCENGINE_SESSION_TOKEN="your-session-token" # 可选
62-
```
63-
64-
在 VeFaaS 环境中,Agent Identity 会自动从 `/var/run/secrets/iam/credential` 读取凭证。
65-
6656
## 示例
6757

6858
```python

docs/content/91.auth/3.oauth2-m2m-outbound.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ OAuth2 M2M(Machine to Machine)认证用于服务间通信,比 API Key 更
1919

2020
### 方式一:使用内置 Vendor(推荐)
2121

22-
选择提供商类型:**Google****GitHub****Coze**,填写:
22+
选择提供商类型:**Lark****Coze****Google****GitHub** ,填写:
2323
- Client ID
2424
- Client Secret
2525

docs/content/91.auth/4.oauth2-user-federation-outbound.md

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ OAuth2 USER_FEDERATION 认证用于用户委托场景,应用代表用户访问
1818

1919
### 方式一:使用内置 Vendor(推荐)
2020

21-
选择提供商类型:**Google****GitHub****Coze**,填写:
21+
选择提供商类型:**Lark****Coze****Google****GitHub** ,填写:
2222
- Client ID
2323
- Client Secret
2424
- 回调 URL(选择[回调地址](./4.oauth2-user-federation-outbound.md#回调地址)对应区域的地址)
@@ -108,45 +108,52 @@ toolset = VeIdentityMcpToolset(
108108

109109
```python
110110
import asyncio
111-
from veadk import Agent, Runner
112-
from veadk.integrations.ve_identity import VeIdentityMcpToolset, oauth2_auth
113-
from google.adk.agents.mcp import StdioServerParameters
111+
from veadk import Agent
112+
from veadk.integrations.ve_identity import (
113+
VeIdentityMcpToolset,
114+
oauth2_auth,
115+
)
116+
from google.adk.tools.mcp_tool.mcp_session_manager import (
117+
StreamableHTTPConnectionParams,
118+
)
119+
120+
from veadk.integrations.ve_identity.auth_processor import AuthRequestProcessor
114121

115-
# GitHub 工具集
116-
github_tools = VeIdentityMcpToolset(
122+
# ECS 工具集
123+
ecs_tools = VeIdentityMcpToolset(
117124
auth_config=oauth2_auth(
118-
provider_name="github-oauth2-provider",
119-
scopes=["repo", "workflow"],
125+
provider_name="volc-ecs-oauth2-provider",
126+
scopes=["read"],
120127
auth_flow="USER_FEDERATION",
121-
callback_url="https://your-app.com/oauth/callback",
122128
),
123-
connection_params=StdioServerParameters(
124-
command="npx",
125-
args=["@modelcontextprotocol/server-github"],
129+
connection_params=StreamableHTTPConnectionParams(
130+
url="https://ecs.mcp.volcbiz.com/ecs/mcp",
126131
),
127132
)
128133

129-
# Google Drive 工具集
130-
drive_tools = VeIdentityMcpToolset(
134+
# 云助手 工具集
135+
cloud_assistant_tools = VeIdentityMcpToolset(
131136
auth_config=oauth2_auth(
132-
provider_name="google-oauth2-provider",
133-
scopes=["https://www.googleapis.com/auth/drive"],
137+
provider_name="volc-ecs-oauth2-provider",
138+
scopes=["read"],
134139
auth_flow="USER_FEDERATION",
135-
callback_url="https://your-app.com/oauth/callback",
136140
),
137-
connection_params=StdioServerParameters(
138-
command="npx",
139-
args=["@modelcontextprotocol/server-google-drive"],
141+
connection_params=StreamableHTTPConnectionParams(
142+
url="https://ecs.mcp.volcbiz.com/cloud_assistant/mcp",
140143
),
141144
)
142145

143146
agent = Agent(
144-
tools=[github_tools, drive_tools],
145-
system_prompt="你是一个多服务集成助手",
147+
tools=[ecs_tools, cloud_assistant_tools],
148+
system_prompt="你是火山引擎ECS助手,可以查询ECS实例信息并执行服务器命令。",
149+
auth_request_processor=AuthRequestProcessor(),
146150
)
147151

148-
runner = Runner(agent=agent)
149-
asyncio.run(runner.run(messages="从 GitHub 获取最新代码变更并保存到 Google Drive"))
152+
asyncio.run(
153+
agent.run(
154+
prompt="先查询我的ECS实例列表,然后选择一个运行中的实例,在上面执行 'uname -a && df -h && free -m' 命令来检查系统信息、磁盘空间和内存使用情况"
155+
)
156+
)
150157
```
151158

152159
## 常见问题

tests/test_ve_identity_auth_config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def on_auth_url_callback(url: str):
103103

104104
def test_oauth2_auth_empty_scopes(self):
105105
"""Test that empty scopes raises ValueError."""
106-
with pytest.raises(ValueError, match="scopes cannot be empty"):
106+
with pytest.raises(ValueError, match="scopes cannot be an empty list"):
107107
oauth2_auth(
108108
provider_name="github",
109109
scopes=[],

veadk/agent.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -298,15 +298,13 @@ async def run(
298298
final_output = ""
299299
for _prompt in prompt:
300300
message = types.Content(role="user", parts=[types.Part(text=_prompt)])
301-
final_output = await self._run(
302-
runner, user_id, session_id, message, stream, auth_request_processor
303-
)
301+
final_output = await self._run(runner, user_id, session_id, message, stream, auth_request_processor)
304302

305303
# VeADK features
306304
if save_session_to_memory:
307-
assert (
308-
self.long_term_memory is not None
309-
), "Long-term memory is not initialized in agent"
305+
assert self.long_term_memory is not None, (
306+
"Long-term memory is not initialized in agent"
307+
)
310308
session = await session_service.get_session(
311309
app_name=app_name,
312310
user_id=user_id,

veadk/integrations/ve_identity/auth_config.py

Lines changed: 25 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from abc import ABC, abstractmethod
2222
from typing import Any, Callable, List, Literal, Optional, Union
2323

24-
from pydantic import BaseModel, model_validator, field_validator
24+
from pydantic import BaseModel, field_validator
2525

2626
from veadk.integrations.ve_identity.models import OAuth2AuthPoller
2727
from veadk.integrations.ve_identity.identity_client import IdentityClient
@@ -35,6 +35,7 @@ def _get_default_region() -> str:
3535
"""
3636
try:
3737
from veadk.config import settings
38+
3839
return settings.veidentity.region
3940
except Exception:
4041
# Fallback to default if config loading fails
@@ -52,8 +53,8 @@ class AuthConfig(BaseModel, ABC):
5253

5354
def __init__(self, **data):
5455
"""Initialize AuthConfig with default region from VeADK config if not provided."""
55-
if 'region' not in data or data['region'] is None:
56-
data['region'] = _get_default_region()
56+
if "region" not in data or data["region"] is None:
57+
data["region"] = _get_default_region()
5758
super().__init__(**data)
5859

5960
@field_validator("provider_name")
@@ -82,10 +83,10 @@ def auth_type(self) -> str:
8283
class OAuth2AuthConfig(AuthConfig):
8384
"""OAuth2 authentication configuration."""
8485

85-
# Required fields
86-
scopes: List[str]
87-
auth_flow: Literal["M2M", "USER_FEDERATION"]
88-
# Optional fields
86+
# Optional fields - control plane will use defaults if not provided
87+
scopes: Optional[List[str]] = None
88+
auth_flow: Optional[Literal["M2M", "USER_FEDERATION"]] = None
89+
# Additional optional fields
8990
callback_url: Optional[str] = None
9091
force_authentication: bool = False
9192
response_for_auth_required: Optional[Union[dict, str]] = None
@@ -94,10 +95,18 @@ class OAuth2AuthConfig(AuthConfig):
9495

9596
@field_validator("scopes")
9697
@classmethod
97-
def validate_scopes_not_empty(cls, v: List[str]) -> List[str]:
98-
"""Validate that scopes list is not empty and contains valid scope strings."""
98+
def validate_scopes_not_empty(cls, v: Optional[List[str]]) -> Optional[List[str]]:
99+
"""Validate that scopes list is not empty and contains valid scope strings.
100+
101+
If scopes is None, the control plane will use default scopes.
102+
"""
103+
if v is None:
104+
return None
105+
99106
if not v:
100-
raise ValueError("scopes cannot be empty")
107+
raise ValueError(
108+
"scopes cannot be an empty list; use None to use control plane defaults"
109+
)
101110

102111
# Validate each scope is not empty
103112
for scope in v:
@@ -128,15 +137,6 @@ def validate_callback_url(cls, v: Optional[str]) -> Optional[str]:
128137
raise ValueError("callback_url must be a valid HTTP/HTTPS URL")
129138
return v
130139

131-
@model_validator(mode="after")
132-
def _validate_required_fields(self):
133-
"""Validate required fields."""
134-
if not self.scopes:
135-
raise ValueError("scopes is required for OAuth2AuthConfig")
136-
if not self.auth_flow:
137-
raise ValueError("auth_flow is required for OAuth2AuthConfig")
138-
return self
139-
140140
@property
141141
def auth_type(self) -> str:
142142
return "oauth2"
@@ -201,8 +201,8 @@ def workload_auth(
201201

202202
def oauth2_auth(
203203
provider_name: str,
204-
scopes: List[str],
205-
auth_flow: Literal["M2M", "USER_FEDERATION"],
204+
scopes: Optional[List[str]] = None,
205+
auth_flow: Optional[Literal["M2M", "USER_FEDERATION"]] = None,
206206
callback_url: Optional[str] = None,
207207
force_authentication: bool = False,
208208
response_for_auth_required: Optional[Union[dict, str]] = None,
@@ -215,8 +215,10 @@ def oauth2_auth(
215215
216216
Args:
217217
provider_name: Name of the credential provider.
218-
scopes: List of OAuth2 scopes.
219-
auth_flow: Authentication flow type ("M2M" or "USER_FEDERATION").
218+
scopes: Optional list of OAuth2 scopes. If not provided, the control plane
219+
will use the default configured scopes for the provider.
220+
auth_flow: Optional authentication flow type ("M2M" or "USER_FEDERATION").
221+
If not provided, the control plane will use the default configured flow.
220222
callback_url: Optional callback URL for OAuth2.
221223
force_authentication: Whether to force authentication.
222224
response_for_auth_required: Response to return when auth is required.

veadk/integrations/ve_identity/auth_mixins.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -343,8 +343,8 @@ class OAuth2AuthMixin(BaseAuthMixin):
343343
def __init__(
344344
self,
345345
*,
346-
scopes: List[str],
347-
auth_flow: Literal["M2M", "USER_FEDERATION"],
346+
scopes: Optional[List[str]] = None,
347+
auth_flow: Optional[Literal["M2M", "USER_FEDERATION"]] = None,
348348
callback_url: Optional[str] = None,
349349
force_authentication: bool = False,
350350
response_for_auth_required: Optional[str] = None,
@@ -356,9 +356,11 @@ def __init__(
356356
"""Initialize the OAuth2 authentication mixin.
357357
358358
Args:
359-
scopes: List of OAuth2 scopes to request.
360-
auth_flow: Authentication flow type - "M2M" for machine-to-machine or
361-
"USER_FEDERATION" for user-delegated access.
359+
scopes: Optional list of OAuth2 scopes to request. If not provided,
360+
the control plane will use the default configured scopes.
361+
auth_flow: Optional authentication flow type - "M2M" for machine-to-machine or
362+
"USER_FEDERATION" for user-delegated access. If not provided,
363+
the control plane will use the default configured flow.
362364
callback_url: OAuth2 redirect URL (must be pre-registered).
363365
force_authentication: If True, forces re-authentication even if cached token exists.
364366
response_for_auth_required: Custom response to return when user authorization is needed.

veadk/integrations/ve_identity/auth_processor.py

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -230,21 +230,19 @@ async def process_auth_request(
230230
on_auth_url(auth_uri)
231231

232232
# Use custom poller or default poller
233-
# Create async token fetcher for default poller
234-
async def async_token_fetcher():
235-
response = self._identity_client.get_oauth2_token_or_auth_url(
236-
**request_dict
237-
)
238-
return (
239-
OAuth2Auth(access_token=response.access_token)
240-
if response.access_token and response.access_token.strip()
241-
else None
242-
)
243-
244233
active_poller = (
245234
self.config.oauth2_auth_poller(auth_uri, request_dict)
246235
if self.config.oauth2_auth_poller
247-
else _DefaultOauth2AuthPoller(auth_uri, async_token_fetcher)
236+
else _DefaultOauth2AuthPoller(
237+
auth_uri,
238+
lambda: (
239+
lambda response: (
240+
OAuth2Auth(access_token=response.access_token)
241+
if response.access_token and response.access_token.strip()
242+
else None
243+
)
244+
)(self._identity_client.get_oauth2_token_or_auth_url(**request_dict)),
245+
)
248246
)
249247

250248
# Poll for the oauth2 auth

0 commit comments

Comments
 (0)