mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-04-25 11:18:22 +00:00
Add new application structure: - app/main.py - application entry point - app/plugins/ - plugin system with auth plugin: - api/ - REST API endpoints and schemas - authorization/ - auth policies, providers, hooks - domain/ - business logic (service, models, jwt, password) - injection/ - route injection and guards - ops/ - operational utilities - runtime/ - runtime configuration - security/ - middleware, CSRF, dependencies - storage/ - user repositories and models - app/static/ - static assets (scalar.js for API docs) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
85 lines
2.3 KiB
Python
85 lines
2.3 KiB
Python
"""Authorization requirement and policy evaluation helpers."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Awaitable, Callable, Mapping
|
|
from dataclasses import dataclass
|
|
from typing import Any
|
|
|
|
from fastapi import HTTPException, Request
|
|
|
|
from app.plugins.auth.authorization.policies import require_thread_owner
|
|
from app.plugins.auth.authorization.types import AuthContext
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PermissionRequirement:
|
|
"""Authorization requirement for a single route action."""
|
|
|
|
resource: str
|
|
action: str
|
|
owner_check: bool = False
|
|
require_existing: bool = False
|
|
|
|
@property
|
|
def permission(self) -> str:
|
|
return f"{self.resource}:{self.action}"
|
|
|
|
|
|
PolicyEvaluator = Callable[[Request, AuthContext, PermissionRequirement, Mapping[str, Any]], Awaitable[None]]
|
|
|
|
|
|
def ensure_authenticated(auth: AuthContext) -> None:
|
|
if not auth.is_authenticated:
|
|
raise HTTPException(status_code=401, detail="Authentication required")
|
|
|
|
|
|
def ensure_capability(auth: AuthContext, requirement: PermissionRequirement) -> None:
|
|
if not auth.has_permission(requirement.resource, requirement.action):
|
|
raise HTTPException(status_code=403, detail=f"Permission denied: {requirement.permission}")
|
|
|
|
|
|
async def evaluate_owner_policy(
|
|
request: Request,
|
|
auth: AuthContext,
|
|
requirement: PermissionRequirement,
|
|
route_params: Mapping[str, Any],
|
|
) -> None:
|
|
if not requirement.owner_check:
|
|
return
|
|
|
|
thread_id = route_params.get("thread_id")
|
|
if thread_id is None:
|
|
raise ValueError("require_permission with owner_check=True requires 'thread_id' parameter")
|
|
|
|
await require_thread_owner(
|
|
request,
|
|
auth,
|
|
thread_id=thread_id,
|
|
require_existing=requirement.require_existing,
|
|
)
|
|
|
|
|
|
async def evaluate_requirement(
|
|
request: Request,
|
|
auth: AuthContext,
|
|
requirement: PermissionRequirement,
|
|
route_params: Mapping[str, Any],
|
|
*,
|
|
policy_evaluators: tuple[PolicyEvaluator, ...],
|
|
) -> None:
|
|
ensure_authenticated(auth)
|
|
ensure_capability(auth, requirement)
|
|
for evaluator in policy_evaluators:
|
|
await evaluator(request, auth, requirement, route_params)
|
|
|
|
|
|
__all__ = [
|
|
"PermissionRequirement",
|
|
"PolicyEvaluator",
|
|
"ensure_authenticated",
|
|
"ensure_capability",
|
|
"evaluate_owner_policy",
|
|
"evaluate_requirement",
|
|
]
|