mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 08:51:58 +00:00
0d9f1d9339
- 实测 /api/evals/calibers、/api/schedules/trigger-options 按 Workflow.name 排序遇同名并列时 SQLite、PostgreSQL 顺序不同。用 AST 逐个过了一遍 order_by:最后一个排序键是 created_at / updated_at / last_active_at / at / name / 各种 *_at 之类、不唯一的列,又不带 DISTINCT、GROUP BY 的,补「, 模型.id」(降序补 id.desc()),共 104 处,分布在 api、apps、auth、blueprints、chat、data、engine、eval、ledger、look、memory、process、providers、scheduler(runs.py 里的三处在上一个提交里) - 按唯一列(seq、version、唯一的 name)排的不动;按聚合、coalesce 之类表达式排的说明见 db/postgres
84 lines
4.0 KiB
Python
84 lines
4.0 KiB
Python
"""应用级权限(§3.12):use 能用(入口、视图、自己的实例);observe 另能看全部实例;manage 能搭建、发布、授权、管钩子。
|
||
|
||
- 关登录:全部放行,等同 manage(界面上挂横幅说明,签批人是自报的署名);
|
||
- admin:所有应用 manage;
|
||
- builder:所有应用至少 observe(能用、能看全部实例);自己建的、或者有 manage 授权的才 manage;
|
||
- user:按授权(给本人、给所在的组、给所有人)取最高的那一档,最高到 observe:搭建、发布、绑定岗位、授权、钩子、按草稿
|
||
预览、改派催办都不给 user 角色(§3.12),授给它的 manage 按 observe 算。没有授权就看不到这个应用。
|
||
- 个人应用(scope=personal,docs/design/normal-user-apps.md N7):只有拥有者能用,按 manage 算(改页面、刷新、提交审核);
|
||
别人一律没有权限、看不到,管理员也一样——管理员只在应用列表里看得到它存在、能归档(can_archive),不看内容(P4)。
|
||
个人应用没有授权(不能分享),授权表里不会有它。
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from sqlalchemy import select
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from app.api.deps import Principal
|
||
from app.db.models_apps import AppGrant
|
||
|
||
RANK = {"use": 1, "observe": 2, "manage": 3}
|
||
#: 三档授权的叫法(同前端 GRANT_ROLE_LABEL):权限不够的报错、授权核对的说明里写这个
|
||
ROLE_TEXT = {"use": "使用", "observe": "查看全部实例", "manage": "管理"}
|
||
|
||
|
||
def at_least(role: str | None, need: str) -> bool:
|
||
return RANK.get(role or "", 0) >= RANK[need]
|
||
|
||
|
||
def granted_role(principal: Principal, grants: list[Any]) -> str | None:
|
||
"""授权里这个人能拿到的最高一档。"""
|
||
best: str | None = None
|
||
groups = set(principal.groups)
|
||
for g in grants:
|
||
hit = (g.principal_kind == "everyone"
|
||
or (g.principal_kind == "user" and principal.username and g.principal == principal.username)
|
||
or (g.principal_kind == "group" and g.principal in groups))
|
||
if hit and RANK.get(g.role, 0) > RANK.get(best or "", 0):
|
||
best = g.role
|
||
return best
|
||
|
||
|
||
def personal_role(principal: Principal, app: Any) -> str | None:
|
||
"""个人应用上这个人的权限:拥有者 manage,别人 None。关登录时全部放行(没有可信的身份,同共享应用)。"""
|
||
if principal.mode == "off":
|
||
return "manage"
|
||
return "manage" if app.owner and principal.username == app.owner else None
|
||
|
||
|
||
def is_personal(app: Any) -> bool:
|
||
return getattr(app, "scope", "shared") == "personal"
|
||
|
||
|
||
def can_archive(principal: Principal, app: Any, role: str | None) -> bool:
|
||
"""能不能归档:管理权限;个人应用另外管理员也能(看不到内容,但能把它收起来)。"""
|
||
return at_least(role, "manage") or (is_personal(app) and principal.role == "admin")
|
||
|
||
|
||
def role_of(principal: Principal, app: Any, grants: list[Any]) -> str | None:
|
||
if is_personal(app):
|
||
return personal_role(principal, app)
|
||
if principal.mode == "off" or principal.role == "admin":
|
||
return "manage"
|
||
granted = granted_role(principal, grants)
|
||
if principal.role == "builder":
|
||
if app.created_by and principal.username and app.created_by == principal.username:
|
||
return "manage"
|
||
return granted if at_least(granted, "manage") else "observe"
|
||
return "observe" if at_least(granted, "manage") else granted
|
||
|
||
|
||
async def grants_of(session: AsyncSession, app_id: str) -> list[AppGrant]:
|
||
return list((await session.execute(select(AppGrant).where(AppGrant.app_id == app_id)
|
||
.order_by(AppGrant.created_at, AppGrant.id))).scalars())
|
||
|
||
|
||
async def app_role(session: AsyncSession, principal: Principal, app: Any) -> str | None:
|
||
if is_personal(app):
|
||
return personal_role(principal, app)
|
||
if principal.mode == "off" or principal.role == "admin":
|
||
return "manage"
|
||
return role_of(principal, app, await grants_of(session, app.id))
|