fix(api): harden facade fallbacks and secret permissions

This commit is contained in:
huanghuoguoguo
2026-10-11 02:33:23 +08:00
parent 1642e6b4f6
commit 15b6aa82a7
+63 -1
View File
@@ -83,6 +83,12 @@ class ApplicationAPI:
include_secret: bool = False,
) -> list[dict]:
self._authorize(context, 'pipeline.list')
# Secret-bearing pipeline configuration must never be exposed merely
# because a caller supplied ``include_secret=True``. The HTTP route
# derives this flag from the same permission, but MCP/assistant callers
# can invoke the facade directly.
if include_secret:
require_permission(context, Permission.RESOURCE_MANAGE)
return await self.ap.pipeline_service.get_pipelines(
context,
sort_by,
@@ -98,6 +104,8 @@ class ApplicationAPI:
include_secret: bool = False,
) -> dict | None:
self._authorize(context, 'pipeline.get')
if include_secret:
require_permission(context, Permission.RESOURCE_MANAGE)
return await self.ap.pipeline_service.get_pipeline(
context,
pipeline_uuid,
@@ -106,7 +114,61 @@ class ApplicationAPI:
async def get_pipeline_extensions(self, context: RequestContext, pipeline_uuid: str) -> dict:
self._authorize(context, 'pipeline.extensions.get')
return await self.ap.pipeline_service.get_pipeline_extensions(context, pipeline_uuid)
getter = getattr(self.ap.pipeline_service, 'get_pipeline_extensions', None)
if callable(getter):
return await getter(context, pipeline_uuid)
# Compatibility path for lightweight service doubles and older service
# graphs. The production PipelineService owns this implementation;
# this fallback keeps the shared facade usable while a graph is being
# assembled and preserves its fail-closed normalization semantics.
from ...pipeline.extension_preferences import normalize_extension_preferences
from ..http.service.secrets import redact_secrets
pipeline = await self.ap.pipeline_service.get_pipeline(context, pipeline_uuid)
if pipeline is None:
return None
connector = getattr(self.ap, 'plugin_connector', None)
plugins = []
if connector is not None:
if getattr(connector, 'is_enable_plugin', False):
require_context = getattr(connector, 'require_workspace_context', None)
if callable(require_context):
await require_context(context)
list_plugins = getattr(connector, 'list_plugins', None)
if callable(list_plugins):
plugins = await list_plugins(component_kinds=['Command', 'EventListener', 'Tool'])
skill_service = getattr(self.ap, 'skill_service', None)
available_skills = []
if skill_service is not None:
try:
available_skills = await skill_service.list_skills(context)
except Exception as exc:
logger = getattr(self.ap, 'logger', None)
warning = getattr(logger, 'warning', None)
if callable(warning):
warning('Unable to list skills for pipeline extensions: %s', exc)
mcp_service = getattr(self.ap, 'mcp_service', None)
available_mcp_servers = []
if mcp_service is not None:
list_servers = getattr(mcp_service, 'get_mcp_servers', None)
if callable(list_servers):
available_mcp_servers = await list_servers(context, contain_runtime_info=True)
prefs = normalize_extension_preferences(pipeline.get('extensions_preferences'))
return {
'enable_all_plugins': prefs.get('enable_all_plugins', True),
'enable_all_mcp_servers': prefs.get('enable_all_mcp_servers', True),
'enable_all_skills': prefs.get('enable_all_skills', True),
'bound_plugins': prefs.get('plugins', []),
'available_plugins': redact_secrets(plugins),
'bound_mcp_servers': prefs.get('mcp_servers', []),
'available_mcp_servers': available_mcp_servers,
'bound_mcp_resources': prefs.get('mcp_resources', []),
'mcp_resource_agent_read_enabled': prefs.get('mcp_resource_agent_read_enabled', True),
'bound_skills': prefs.get('skills', []),
'available_skills': available_skills,
}
async def update_pipeline_extensions(self, context: RequestContext, pipeline_uuid: str, **kwargs) -> None:
self._authorize(context, 'pipeline.extensions.update')