mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-08-13 16:28:38 +00:00
* feat(authz): add built-in RBAC provider and provider factory (Phase 1A-2, #4063) Phase 1A-2: RBAC provider + provider factory. No runtime behavior change. New authz/rbac.py — RbacAuthorizationProvider: - allow: '*' / True / list / [] / missing → deny-wins semantics - deny always overrides all allow forms - resource name explicit mapping (tool→tools, model→models, etc.) - unknown/missing role raises ValueError (never silent allow) - config compiled to immutable frozensets at construction - filter_resources preserves order, no mutation, consistent with authorize - sync == async decisions New authz/runtime.py — resolve_authorization_provider: - disabled → None (no import attempted) - enabled + no provider → ValueError - invalid class path / construction failure → ValueError with path - isinstance Protocol check post-construction - no caching, no fail_closed/default_role injection 48 tests (37 RBAC + 11 factory). No config schema change, no config_version bump. Per RFC #4063 Phase 1A-2. Layer 1/Layer 2 wiring deferred to Phase 1B. * fix(authz): reject unknown RBAC provider config Fail fast on misspelled top-level RBAC settings, cover factory error propagation, and record Phase 1B policy and audit caveats. * fix(authz): reject unreachable resource aliases Fail fast when RBAC config uses reserved request-side aliases, preserve same-name and custom resources, and add regression coverage for every mapped alias. * fix(authz): validate RBAC request identifiers
51 lines
2.0 KiB
Python
51 lines
2.0 KiB
Python
"""Provider factory — resolves and constructs the configured AuthorizationProvider.
|
|
|
|
This is the single entry point for creating an authorization provider from
|
|
``AuthorizationConfig``. It does not cache instances (Phase 1B resolves once
|
|
per agent build and passes the same instance to Layer 1 and Layer 2).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from deerflow.authz.provider import AuthorizationProvider
|
|
from deerflow.config.authorization_config import AuthorizationConfig
|
|
from deerflow.reflection import resolve_variable
|
|
|
|
|
|
def resolve_authorization_provider(
|
|
config: AuthorizationConfig,
|
|
) -> AuthorizationProvider | None:
|
|
"""Resolve the authorization provider from config.
|
|
|
|
Returns:
|
|
A constructed ``AuthorizationProvider`` instance, or ``None`` if
|
|
authorization is disabled.
|
|
|
|
Raises:
|
|
ValueError: If ``enabled`` is True but no provider is configured,
|
|
or if the class path is invalid / construction fails / the
|
|
instance does not satisfy the ``AuthorizationProvider`` Protocol.
|
|
"""
|
|
if not config.enabled:
|
|
return None
|
|
|
|
if config.provider is None:
|
|
raise ValueError("authorization.enabled is true but no provider is configured; set authorization.provider.use to a class path")
|
|
|
|
class_path = config.provider.use
|
|
try:
|
|
provider_cls = resolve_variable(class_path, expected_type=type)
|
|
except (ImportError, ValueError) as err:
|
|
raise ValueError(f"Failed to resolve authorization provider class '{class_path}': {err}") from err
|
|
|
|
kwargs = dict(config.provider.config) if config.provider.config else {}
|
|
try:
|
|
instance = provider_cls(**kwargs)
|
|
except Exception as err:
|
|
raise ValueError(f"Failed to construct authorization provider '{class_path}': {err}") from err
|
|
|
|
if not isinstance(instance, AuthorizationProvider):
|
|
raise ValueError(f"Authorization provider '{class_path}' does not satisfy the AuthorizationProvider Protocol")
|
|
|
|
return instance
|