| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- from __future__ import annotations
- from collections.abc import Iterable
- from functools import wraps
- from flask import current_app, g, jsonify, request
- from app.models.result import failed
- READ_GOVERNANCE = "governance:read"
- EDIT_GOVERNANCE = "governance:edit"
- APPROVE_REVIEW = "review:approve"
- MANAGE_USERS = "users:manage"
- ACTIVATE_WORKFLOW = "workflow:activate"
- OPERATE_ORDERS = "orders:operate"
- DATASOURCE_POOL_MANAGE = "datasources:pools:manage"
- KNOWLEDGE_MANAGE = "knowledge:manage"
- ROLE_PERMISSIONS = {
- "viewer": frozenset({READ_GOVERNANCE}),
- "editor": frozenset(
- {READ_GOVERNANCE, EDIT_GOVERNANCE, APPROVE_REVIEW, OPERATE_ORDERS}
- ),
- "admin": frozenset(
- {
- READ_GOVERNANCE,
- EDIT_GOVERNANCE,
- APPROVE_REVIEW,
- MANAGE_USERS,
- ACTIVATE_WORKFLOW,
- OPERATE_ORDERS,
- DATASOURCE_POOL_MANAGE,
- KNOWLEDGE_MANAGE,
- }
- ),
- }
- PUBLIC = "public"
- def permission_for_request(path: str, method: str) -> tuple[str, ...]:
- """Classify every API request in one auditable, deny-by-default policy."""
- method = method.upper()
- if path in {"/api/system/health", "/api/system/auth/login"}:
- return (PUBLIC,)
- if path in {"/api/knowledge/search", "/api/knowledge/ask"}:
- return (READ_GOVERNANCE,)
- if path.startswith("/api/knowledge/admin"):
- return (KNOWLEDGE_MANAGE,)
- if path.startswith("/api/system/users"):
- return (MANAGE_USERS,)
- if path.startswith("/api/system/workbench"):
- return (READ_GOVERNANCE,)
- if (
- path == "/api/datasource/pools"
- or (
- path.startswith("/api/datasource/")
- and (
- path.endswith("/pool")
- or path.endswith("/pool/invalidate")
- )
- )
- ):
- return (DATASOURCE_POOL_MANAGE,)
- if path == "/api/datasource/list":
- return (READ_GOVERNANCE,)
- if path.startswith("/api/system/") and not path.startswith("/api/system/auth/me"):
- return (MANAGE_USERS,)
- if path.startswith("/api/dataflow/") and any(
- marker in path.lower() for marker in ("activate", "publish", "execute")
- ):
- return (ACTIVATE_WORKFLOW,)
- if "order" in path.lower() and method != "GET":
- return (OPERATE_ORDERS,)
- if method == "GET":
- return (READ_GOVERNANCE,)
- if method in {"POST", "PUT", "PATCH", "DELETE"}:
- return (EDIT_GOVERNANCE,)
- return (MANAGE_USERS,)
- def permissions_for_roles(roles: Iterable[str]) -> frozenset[str]:
- result: set[str] = set()
- for role in roles:
- result.update(ROLE_PERMISSIONS.get(role, ()))
- return frozenset(result)
- def authenticate_request() -> dict | None:
- from app.core.system.auth import load_identity_from_token
- existing = getattr(g, "current_user", None)
- if existing:
- return existing
- header = request.headers.get("Authorization", "")
- if not header.startswith("Bearer "):
- return None
- token = header[7:].strip()
- if not token:
- return None
- return load_identity_from_token(token, secret=current_app.config["SECRET_KEY"])
- def configure_api_authorization(app) -> None:
- @app.before_request
- def enforce_api_policy():
- if not request.path.startswith("/api/") or request.method == "OPTIONS":
- return None
- required = permission_for_request(request.path, request.method)
- if required == (PUBLIC,):
- return None
- identity = authenticate_request()
- if identity is None:
- return jsonify(failed("未登录或登录已过期", code=401)), 401
- permissions = permissions_for_roles(identity["roles"])
- if not set(required).issubset(permissions):
- return jsonify(failed("权限不足", code=403)), 403
- identity["permissions"] = sorted(permissions)
- g.current_user = identity
- return None
- def require_permissions(*required: str):
- def decorator(view):
- @wraps(view)
- def wrapped(*args, **kwargs):
- identity = authenticate_request()
- if identity is None:
- return jsonify(failed("未登录或登录已过期", code=401)), 401
- permissions = permissions_for_roles(identity["roles"])
- if not set(required).issubset(permissions):
- return jsonify(failed("权限不足", code=403)), 403
- identity["permissions"] = sorted(permissions)
- g.current_user = identity
- return view(*args, **kwargs)
- wrapped.required_permissions = tuple(required)
- return wrapped
- return decorator
|