Files
AgentLab/backend/app/apps/access.py
T
ThearchyHelios 277982ead3 普通用户建应用(后端):
- 蓝图:bp_* 操作和逐卡白名单、蓝图检查、留痕、按蓝图判权限;五个场景模板加自由形(骨架、关节、应用骨架、定时任务),实例化与骨架比对;BlueprintApplier / TemplateApplier(只收模板声明过的关节)与自查;look 新类别口径卡;蓝图对话和填关节两个流式接口,不同组的关节并行填、出错的组串行重试;模板列表
- 试运行与发布单:四种起点(对话、模板、问数据的一轮、已发布版本);试运行是探索运行,草稿应用按这次运行解析、印章写试运行,流程类派给自己、不触发升级;发布单九步、预检只读、每步留痕、停下后接着做、核对卡片指纹和应用管理权、启动时整理中断的单;白话层覆盖全部问题编号
- 记录表与文件:平台托管只追加的记录表、按高水位固定数据、连接上的临时视图做行级可见、写入走动作(records_append)、表版本在发布时切换、草稿不影响线上、记录列表遮敏感列;文件按哈希存、两步上传、提交时绑定运行、下载按读权限、清理、file_text、按配方导入一期(table_import_period)
- 个人应用:存成个人应用(问数据的约束)、刷新(遮罩、只读、每日上限)、只有拥有者看得见、提交审核与共享副本、开关下发到认人接口;页面编辑的候选;隐藏组件不下发;三条新检查
2026-10-15 12:09:29 +08:00

84 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""应用级权限(§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))).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))