mirror of
https://github.com/DrizzleTime/Foxel.git
synced 2026-05-10 17:43:35 +08:00
refactor: remove Permission model and update related code to use permission codes
This commit is contained in:
@@ -3,9 +3,7 @@ from fastapi import HTTPException
|
||||
|
||||
from models.database import (
|
||||
UserAccount,
|
||||
Role,
|
||||
UserRole,
|
||||
Permission,
|
||||
RolePermission,
|
||||
PathRule,
|
||||
)
|
||||
@@ -144,13 +142,8 @@ class PermissionService:
|
||||
if not role_ids:
|
||||
return False
|
||||
|
||||
# 检查角色是否有该权限
|
||||
permission = await Permission.get_or_none(code=permission_code)
|
||||
if not permission:
|
||||
return False
|
||||
|
||||
role_permission = await RolePermission.filter(
|
||||
role_id__in=role_ids, permission_id=permission.id
|
||||
role_id__in=role_ids, permission_code=permission_code
|
||||
).first()
|
||||
|
||||
return role_permission is not None
|
||||
@@ -180,12 +173,12 @@ class PermissionService:
|
||||
|
||||
# 超级管理员拥有所有权限
|
||||
if user.is_admin:
|
||||
all_permissions = await Permission.all()
|
||||
all_permission_codes = [item["code"] for item in PERMISSION_DEFINITIONS]
|
||||
all_path_rules = await PathRule.all()
|
||||
return UserPermissions(
|
||||
user_id=user_id,
|
||||
is_admin=True,
|
||||
permissions=[p.code for p in all_permissions],
|
||||
permissions=all_permission_codes,
|
||||
path_rules=[
|
||||
PathRuleInfo(
|
||||
id=r.id,
|
||||
@@ -210,10 +203,8 @@ class PermissionService:
|
||||
# 获取权限
|
||||
permissions = []
|
||||
if role_ids:
|
||||
role_permissions = await RolePermission.filter(
|
||||
role_id__in=role_ids
|
||||
).prefetch_related("permission")
|
||||
permissions = list(set(rp.permission.code for rp in role_permissions))
|
||||
role_permissions = await RolePermission.filter(role_id__in=role_ids)
|
||||
permissions = sorted(set(rp.permission_code for rp in role_permissions))
|
||||
|
||||
# 获取路径规则
|
||||
path_rules = []
|
||||
@@ -245,16 +236,14 @@ class PermissionService:
|
||||
@classmethod
|
||||
async def get_all_permissions(cls) -> List[PermissionInfo]:
|
||||
"""获取所有权限定义"""
|
||||
permissions = await Permission.all()
|
||||
return [
|
||||
PermissionInfo(
|
||||
id=p.id,
|
||||
code=p.code,
|
||||
name=p.name,
|
||||
category=p.category,
|
||||
description=p.description,
|
||||
code=item["code"],
|
||||
name=item["name"],
|
||||
category=item["category"],
|
||||
description=item.get("description"),
|
||||
)
|
||||
for p in permissions
|
||||
for item in PERMISSION_DEFINITIONS
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
||||
@@ -49,7 +49,6 @@ PERMISSION_DEFINITIONS = [
|
||||
|
||||
# Pydantic 模型
|
||||
class PermissionInfo(BaseModel):
|
||||
id: int
|
||||
code: str
|
||||
name: str
|
||||
category: str
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
from typing import List
|
||||
from fastapi import HTTPException
|
||||
|
||||
from models.database import Role, RolePermission, Permission, PathRule, UserRole
|
||||
from models.database import Role, RolePermission, PathRule, UserRole
|
||||
from domain.permission.service import PermissionService
|
||||
from domain.permission.types import PathRuleCreate, PathRuleInfo
|
||||
from domain.permission.types import PathRuleCreate, PathRuleInfo, PERMISSION_DEFINITIONS
|
||||
from .types import RoleInfo, RoleDetail, RoleCreate, RoleUpdate, SystemRoles
|
||||
|
||||
|
||||
@@ -33,10 +33,8 @@ class RoleService:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
# 获取权限
|
||||
role_permissions = await RolePermission.filter(role_id=role_id).prefetch_related(
|
||||
"permission"
|
||||
)
|
||||
permissions = [rp.permission.code for rp in role_permissions]
|
||||
role_permissions = await RolePermission.filter(role_id=role_id)
|
||||
permissions = sorted(set(rp.permission_code for rp in role_permissions))
|
||||
|
||||
# 获取路径规则数量
|
||||
path_rules_count = await PathRule.filter(role_id=role_id).count()
|
||||
@@ -126,12 +124,8 @@ class RoleService:
|
||||
if not role:
|
||||
raise HTTPException(404, detail="角色不存在")
|
||||
|
||||
# 获取权限ID
|
||||
permissions = await Permission.filter(code__in=permission_codes)
|
||||
permission_map = {p.code: p.id for p in permissions}
|
||||
|
||||
# 验证所有权限代码
|
||||
invalid_codes = set(permission_codes) - set(permission_map.keys())
|
||||
all_permission_codes = {item["code"] for item in PERMISSION_DEFINITIONS}
|
||||
invalid_codes = set(permission_codes) - all_permission_codes
|
||||
if invalid_codes:
|
||||
raise HTTPException(400, detail=f"无效的权限代码: {', '.join(invalid_codes)}")
|
||||
|
||||
@@ -142,13 +136,13 @@ class RoleService:
|
||||
for code in permission_codes:
|
||||
await RolePermission.create(
|
||||
role_id=role_id,
|
||||
permission_id=permission_map[code],
|
||||
permission_code=code,
|
||||
)
|
||||
|
||||
# 清除权限缓存
|
||||
PermissionService.clear_cache()
|
||||
|
||||
return permission_codes
|
||||
return list(permission_codes)
|
||||
|
||||
@classmethod
|
||||
async def get_role_path_rules(cls, role_id: int) -> List[PathRuleInfo]:
|
||||
@@ -292,36 +286,3 @@ class RoleService:
|
||||
existing = await Role.get_or_none(name=role_data["name"])
|
||||
if not existing:
|
||||
await Role.create(**role_data)
|
||||
|
||||
@classmethod
|
||||
async def setup_admin_role_permissions(cls) -> None:
|
||||
"""为管理员角色设置所有权限"""
|
||||
admin_role = await Role.get_or_none(name=SystemRoles.ADMIN)
|
||||
if not admin_role:
|
||||
return
|
||||
|
||||
# 获取所有权限
|
||||
all_permissions = await Permission.all()
|
||||
|
||||
# 清除现有权限
|
||||
await RolePermission.filter(role_id=admin_role.id).delete()
|
||||
|
||||
# 添加所有权限
|
||||
for perm in all_permissions:
|
||||
await RolePermission.create(role_id=admin_role.id, permission_id=perm.id)
|
||||
|
||||
# 添加全路径访问规则
|
||||
existing_rule = await PathRule.filter(
|
||||
role_id=admin_role.id, path_pattern="/**"
|
||||
).first()
|
||||
if not existing_rule:
|
||||
await PathRule.create(
|
||||
role_id=admin_role.id,
|
||||
path_pattern="/**",
|
||||
is_regex=False,
|
||||
can_read=True,
|
||||
can_write=True,
|
||||
can_delete=True,
|
||||
can_share=True,
|
||||
priority=100,
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ async def get_temp_link(
|
||||
current_user: Annotated[User, Depends(get_current_active_user)],
|
||||
expires_in: int = Query(3600, description="有效时间(秒), 0或负数表示永久"),
|
||||
):
|
||||
await PermissionService.require_path_permission(current_user.id, full_path, PathAction.SHARE)
|
||||
await PermissionService.require_path_permission(current_user.id, full_path, PathAction.READ)
|
||||
data = await VirtualFSService.create_temp_link(full_path, expires_in)
|
||||
return success(data)
|
||||
|
||||
|
||||
2
main.py
2
main.py
@@ -21,6 +21,7 @@ from middleware.exception_handler import (
|
||||
import httpx
|
||||
from dotenv import load_dotenv
|
||||
from domain.tasks import task_queue_service, task_scheduler
|
||||
from domain.role.service import RoleService
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@@ -66,6 +67,7 @@ async def lifespan(app: FastAPI):
|
||||
os.makedirs("data/db", exist_ok=True)
|
||||
os.makedirs("data/plugins", exist_ok=True)
|
||||
await init_db()
|
||||
await RoleService.ensure_system_roles()
|
||||
await runtime_registry.refresh()
|
||||
await ConfigService.set("APP_VERSION", VERSION)
|
||||
await task_queue_service.start_worker()
|
||||
|
||||
@@ -3,7 +3,6 @@ from .database import (
|
||||
UserAccount,
|
||||
Role,
|
||||
UserRole,
|
||||
Permission,
|
||||
RolePermission,
|
||||
PathRule,
|
||||
)
|
||||
@@ -13,7 +12,6 @@ __all__ = [
|
||||
"UserAccount",
|
||||
"Role",
|
||||
"UserRole",
|
||||
"Permission",
|
||||
"RolePermission",
|
||||
"PathRule",
|
||||
]
|
||||
|
||||
@@ -62,19 +62,6 @@ class UserRole(Model):
|
||||
unique_together = (("user", "role"),)
|
||||
|
||||
|
||||
class Permission(Model):
|
||||
"""权限定义表"""
|
||||
|
||||
id = fields.IntField(pk=True)
|
||||
code = fields.CharField(max_length=50, unique=True) # 权限代码
|
||||
name = fields.CharField(max_length=100) # 权限名称
|
||||
category = fields.CharField(max_length=50) # 分类:system/adapter/file
|
||||
description = fields.CharField(max_length=255, null=True)
|
||||
|
||||
class Meta:
|
||||
table = "permissions"
|
||||
|
||||
|
||||
class RolePermission(Model):
|
||||
"""角色-权限关联表"""
|
||||
|
||||
@@ -82,13 +69,11 @@ class RolePermission(Model):
|
||||
role: fields.ForeignKeyRelation[Role] = fields.ForeignKeyField(
|
||||
"models.Role", related_name="role_permissions", on_delete=fields.CASCADE
|
||||
)
|
||||
permission: fields.ForeignKeyRelation[Permission] = fields.ForeignKeyField(
|
||||
"models.Permission", related_name="permission_roles", on_delete=fields.CASCADE
|
||||
)
|
||||
permission_code = fields.CharField(max_length=50)
|
||||
|
||||
class Meta:
|
||||
table = "role_permissions"
|
||||
unique_together = (("role", "permission"),)
|
||||
unique_together = (("role", "permission_code"),)
|
||||
|
||||
|
||||
class PathRule(Model):
|
||||
|
||||
@@ -10,17 +10,12 @@ import sys
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_DB_PATH = PROJECT_ROOT / "data/db/db.sqlite3"
|
||||
if str(PROJECT_ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(PROJECT_ROOT))
|
||||
|
||||
from domain.config import VERSION
|
||||
from domain.auth import get_password_hash
|
||||
from domain.permission.types import PERMISSION_DEFINITIONS
|
||||
from domain.role.types import SystemRoles
|
||||
|
||||
|
||||
def _project_root() -> Path:
|
||||
return PROJECT_ROOT
|
||||
|
||||
|
||||
def _supports_color() -> bool:
|
||||
@@ -67,10 +62,6 @@ def _print_banner() -> None:
|
||||
print(f"{title}\n", file=sys.stderr)
|
||||
|
||||
|
||||
def _default_db_path() -> Path:
|
||||
return _project_root() / "data/db/db.sqlite3"
|
||||
|
||||
|
||||
def _gen_password(length: int) -> str:
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
return "".join(secrets.choice(alphabet) for _ in range(length))
|
||||
@@ -78,19 +69,17 @@ def _gen_password(length: int) -> str:
|
||||
|
||||
def _find_user(conn: sqlite3.Connection, username_or_email: str) -> tuple[int, str] | None:
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("SELECT id, username FROM user WHERE username = ?", (username_or_email,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return int(row[0]), str(row[1])
|
||||
|
||||
cursor.execute("SELECT id, username FROM user WHERE email = ?", (username_or_email,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return int(row[0]), str(row[1])
|
||||
|
||||
normalized = username_or_email.strip().lower()
|
||||
|
||||
candidates = [
|
||||
("username", username_or_email),
|
||||
("email", username_or_email),
|
||||
]
|
||||
if normalized and normalized != username_or_email:
|
||||
cursor.execute("SELECT id, username FROM user WHERE email = ?", (normalized,))
|
||||
candidates.append(("email", normalized))
|
||||
|
||||
for field, value in candidates:
|
||||
cursor.execute(f"SELECT id, username FROM user WHERE {field} = ?", (value,))
|
||||
row = cursor.fetchone()
|
||||
if row:
|
||||
return int(row[0]), str(row[1])
|
||||
@@ -99,7 +88,7 @@ def _find_user(conn: sqlite3.Connection, username_or_email: str) -> tuple[int, s
|
||||
|
||||
|
||||
def _cmd_reset_password(args: argparse.Namespace) -> int:
|
||||
db_path = Path(args.db).expanduser() if args.db else _default_db_path()
|
||||
db_path = Path(args.db).expanduser() if args.db else DEFAULT_DB_PATH
|
||||
|
||||
if args.random:
|
||||
password = _gen_password(args.length)
|
||||
@@ -108,8 +97,7 @@ def _cmd_reset_password(args: argparse.Namespace) -> int:
|
||||
|
||||
hashed_password = get_password_hash(password)
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
try:
|
||||
with sqlite3.connect(str(db_path)) as conn:
|
||||
user = _find_user(conn, args.username_or_email)
|
||||
if not user:
|
||||
print(f"用户不存在: {args.username_or_email}", file=sys.stderr)
|
||||
@@ -120,8 +108,6 @@ def _cmd_reset_password(args: argparse.Namespace) -> int:
|
||||
(hashed_password, user_id),
|
||||
)
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if args.random:
|
||||
print(password)
|
||||
@@ -129,129 +115,6 @@ def _cmd_reset_password(args: argparse.Namespace) -> int:
|
||||
return 0
|
||||
|
||||
|
||||
def _cmd_init_rbac(args: argparse.Namespace) -> int:
|
||||
db_path = Path(args.db).expanduser() if args.db else _default_db_path()
|
||||
|
||||
role_definitions = [
|
||||
{
|
||||
"name": SystemRoles.ADMIN,
|
||||
"description": "管理员角色,拥有所有系统和适配器权限",
|
||||
},
|
||||
{
|
||||
"name": SystemRoles.USER,
|
||||
"description": "普通用户角色,需要管理员配置路径权限",
|
||||
},
|
||||
{
|
||||
"name": SystemRoles.VIEWER,
|
||||
"description": "只读用户角色,仅可查看文件",
|
||||
},
|
||||
]
|
||||
|
||||
conn = sqlite3.connect(str(db_path))
|
||||
try:
|
||||
conn.execute("PRAGMA foreign_keys = ON")
|
||||
cursor = conn.cursor()
|
||||
|
||||
try:
|
||||
cursor.execute("SELECT 1 FROM permissions LIMIT 1")
|
||||
cursor.execute("SELECT 1 FROM roles LIMIT 1")
|
||||
cursor.execute("SELECT 1 FROM role_permissions LIMIT 1")
|
||||
cursor.execute("SELECT 1 FROM path_rules LIMIT 1")
|
||||
except sqlite3.OperationalError as exc:
|
||||
print(f"数据库未初始化(缺少表)。请先启动一次服务生成表。{exc}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# upsert permissions
|
||||
for perm in PERMISSION_DEFINITIONS:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO permissions (code, name, category, description)
|
||||
VALUES (?, ?, ?, ?)
|
||||
ON CONFLICT(code) DO UPDATE SET
|
||||
name = excluded.name,
|
||||
category = excluded.category,
|
||||
description = excluded.description
|
||||
""",
|
||||
(
|
||||
perm["code"],
|
||||
perm["name"],
|
||||
perm["category"],
|
||||
perm.get("description"),
|
||||
),
|
||||
)
|
||||
|
||||
# upsert roles
|
||||
for role in role_definitions:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO roles (name, description, is_system)
|
||||
VALUES (?, ?, 1)
|
||||
ON CONFLICT(name) DO UPDATE SET
|
||||
description = excluded.description,
|
||||
is_system = 1
|
||||
""",
|
||||
(role["name"], role["description"]),
|
||||
)
|
||||
|
||||
# grant all permissions to Admin role
|
||||
cursor.execute("SELECT id FROM roles WHERE name = ?", (SystemRoles.ADMIN,))
|
||||
admin_row = cursor.fetchone()
|
||||
if not admin_row:
|
||||
print("初始化失败:未找到 Admin 角色", file=sys.stderr)
|
||||
return 1
|
||||
admin_role_id = int(admin_row[0])
|
||||
|
||||
cursor.execute("DELETE FROM role_permissions WHERE role_id = ?", (admin_role_id,))
|
||||
cursor.execute("SELECT id FROM permissions")
|
||||
permission_ids = [int(row[0]) for row in cursor.fetchall()]
|
||||
cursor.executemany(
|
||||
"INSERT INTO role_permissions (role_id, permission_id) VALUES (?, ?)",
|
||||
[(admin_role_id, pid) for pid in permission_ids],
|
||||
)
|
||||
|
||||
# ensure Admin has full access path rule
|
||||
cursor.execute(
|
||||
"SELECT id FROM path_rules WHERE role_id = ? AND path_pattern = ? LIMIT 1",
|
||||
(admin_role_id, "/**"),
|
||||
)
|
||||
existing_rule = cursor.fetchone()
|
||||
if existing_rule:
|
||||
cursor.execute(
|
||||
"""
|
||||
UPDATE path_rules
|
||||
SET is_regex = 0,
|
||||
can_read = 1,
|
||||
can_write = 1,
|
||||
can_delete = 1,
|
||||
can_share = 1,
|
||||
priority = 100
|
||||
WHERE id = ?
|
||||
""",
|
||||
(int(existing_rule[0]),),
|
||||
)
|
||||
else:
|
||||
cursor.execute(
|
||||
"""
|
||||
INSERT INTO path_rules (
|
||||
role_id, path_pattern, is_regex,
|
||||
can_read, can_write, can_delete, can_share,
|
||||
priority
|
||||
)
|
||||
VALUES (?, ?, 0, 1, 1, 1, 1, 100)
|
||||
""",
|
||||
(admin_role_id, "/**"),
|
||||
)
|
||||
|
||||
conn.commit()
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
print(f"已初始化权限: {len(PERMISSION_DEFINITIONS)} 条", file=sys.stderr)
|
||||
print("已补齐内置角色: Admin / User / Viewer", file=sys.stderr)
|
||||
print("已为 Admin 角色授予全部权限并设置 /** 全路径规则", file=sys.stderr)
|
||||
return 0
|
||||
|
||||
|
||||
def _build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="foxel")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
@@ -264,10 +127,6 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
reset_password.add_argument("--db", help="sqlite db 路径(默认 data/db/db.sqlite3)")
|
||||
reset_password.set_defaults(func=_cmd_reset_password)
|
||||
|
||||
init_rbac = subparsers.add_parser("init-rbac", help="初始化权限与内置角色")
|
||||
init_rbac.add_argument("--db", help="sqlite db 路径(默认 data/db/db.sqlite3)")
|
||||
init_rbac.set_defaults(func=_cmd_init_rbac)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import request from './client';
|
||||
import type { PathRuleInfo } from './roles';
|
||||
|
||||
export interface PermissionInfo {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
category: string;
|
||||
|
||||
Reference in New Issue
Block a user