diff --git a/inventory-backend/app/utils/decorators.py b/inventory-backend/app/utils/decorators.py index 6bd875d..9dffee2 100644 --- a/inventory-backend/app/utils/decorators.py +++ b/inventory-backend/app/utils/decorators.py @@ -167,14 +167,33 @@ def permission_required(permission_code): return decorator return wrapper +def _has_cross_domain_permission(user_role, user_company=''): + """ + 检查指定角色是否拥有「全局跨域访问」权限。 + 复用 AuthService.get_user_permissions 统一查询。 + """ + if not user_role: + return False + try: + from app.services.auth_service import AuthService + perm_dict = AuthService.get_user_permissions(user_role, company_name=user_company) + all_perms = perm_dict.get('menus', []) + perm_dict.get('elements', []) + return 'crossDomain' in all_perms + except Exception: + return False + + def get_current_company_filter(): """ 多租户数据权限隔离工具函数。 - 根据当前 JWT 中的 role 和 company_name 决定 SQL 查询应过滤到哪个公司。 + 权限逻辑(基于权限码 crossDomain,支持动态分配): + - SUPER_ADMIN → None(全量跨域) + - 拥有 crossDomain 权限 → None(全量跨域) + - 普通用户 → 强制隔离到 JWT 中记录的公司 返回值: - None → 不限制公司(超级管理员全局视角,或显式传了 'ALL') + None → 不限制公司 str → 仅查询该公司数据 使用示例: @@ -194,17 +213,21 @@ def get_current_company_filter(): # 从请求参数获取前端指定的公司(兼容 company_name 和 company 两个参数名) req_company = request.args.get('company_name', '') or request.args.get('company', '') + # 超级管理员 → 全局跨域 if user_role == 'SUPER_ADMIN': - # 超级管理员:允许通过参数指定公司过滤 if req_company and req_company.strip().upper() != 'ALL': return req_company.strip() - # 未指定或指定了 ALL → 返回 None(全量,不限制) return None - else: - # 其他角色(SUPERVISOR / FINANCE / WAREHOUSE_MGR 等): - # 强制隔离到 JWT 中记录的公司,无视前端参数 - user_company = claims.get('company_name', '') - return user_company if user_company else None + + # 动态权限码检查:拥有 crossDomain 权限 → 全局跨域 + user_company = claims.get('company_name', '') + if user_role and _has_cross_domain_permission(user_role, user_company): + if req_company and req_company.strip().upper() != 'ALL': + return req_company.strip() + return None + + # 普通用户 → 强制隔离到本公司 + return user_company if user_company else None def audit_log(module: str = None, action: str = None, get_target_id_fn=None, get_target_name_fn=None, get_details_fn=None): diff --git a/inventory-backend/migrations_add_cross_domain_permission.sql b/inventory-backend/migrations_add_cross_domain_permission.sql new file mode 100644 index 0000000..7d29943 --- /dev/null +++ b/inventory-backend/migrations_add_cross_domain_permission.sql @@ -0,0 +1,47 @@ +-- ============================================================ +-- migrations_add_cross_domain_permission.sql +-- 新增「全局跨域访问」权限节点,支持基于权限码的动态跨公司数据访问控制 +-- +-- 逻辑: +-- - 拥有 crossDomain 权限的角色 → 可查看所有公司数据 +-- - 无此权限的角色 → 只能看本公司数据 +-- +-- 执行方式: +-- psql -h -U postgres -d inventory_system -f migrations_add_cross_domain_permission.sql +-- ============================================================ + +BEGIN; + +-- 1. 向 sys_element 插入跨域权限码(挂 system 菜单下) +INSERT INTO sys_element (menu_code, name, code, element_type) +SELECT 'system_mgmt', '全局跨域访问', 'crossDomain', 'button' +WHERE NOT EXISTS ( + SELECT 1 FROM sys_element WHERE code = 'crossDomain' +); + +-- 2. 向 sys_menu 插入菜单节点(如果 system 菜单下还缺此菜单项) +-- 注意: 这是一个能力型权限,不一定需要前端菜单入口, +-- 如果需要在权限分配页面中可见,保留此 INSERT; +-- 如果纯后端控制,此行可注释。 +INSERT INTO sys_menu (code, name, parent_code, is_visible) +SELECT 'system_cross_domain', '全局跨域访问', 'system_mgmt', true +WHERE NOT EXISTS ( + SELECT 1 FROM sys_menu WHERE code = 'system_cross_domain' +); + +-- 3. 默认分配给 SUPER_ADMIN 角色 +INSERT INTO sys_role_permission (role_code, target_code, type) +SELECT 'SUPER_ADMIN', 'crossDomain', 'element' +WHERE NOT EXISTS ( + SELECT 1 FROM sys_role_permission + WHERE role_code = 'SUPER_ADMIN' AND target_code = 'crossDomain' +); + +-- 4. 验证 +SELECT code, name, menu_code, element_type +FROM sys_element WHERE code = 'crossDomain'; + +SELECT role_code, target_code, type +FROM sys_role_permission WHERE target_code = 'crossDomain'; + +COMMIT;