From 5dfea168d3ded79c417ec97d066b89617fccf5b3 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 14 Jul 2026 17:18:26 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E4=BA=86=20GVSDSDK=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E6=94=B9=E4=BA=86=E9=83=A8=E5=88=86=20UI=20=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- TempLate/1 | 286 +++++++++++++ .../GERP/pages/Company/company-detail.html | 12 +- TempLate/GERP/pages/Company/company.html | 2 +- TempLate/GERP/pages/Company/department.html | 4 +- .../GERP/pages/Company/workgroup-detail.html | 31 +- TempLate/GERP/pages/Company/workgroup.html | 1 + .../GERP/pages/system/userrole-debug.html | 3 +- TempLate/assets/img/Mascot/original/1.png | Bin 114330 -> 17242 bytes TempLate/assets/js/main.js | 35 +- TempLate/index.html | 305 -------------- gvsds/__pycache__/auth.cpython-312.pyc | Bin 2583 -> 6296 bytes .../__pycache__/form_service.cpython-312.pyc | Bin 46389 -> 57059 bytes .../gerp/__pycache__/models.cpython-312.pyc | Bin 44098 -> 45507 bytes .../__pycache__/role_scope.cpython-312.pyc | Bin 7378 -> 7409 bytes gvsds/apps/gerp/form_service.py | 393 +++++++++++++++--- .../gerp/migrations/0013_workgroup_role.py | 35 ++ .../0013_workgroup_role.cpython-312.pyc | Bin 0 -> 2495 bytes gvsds/apps/gerp/models.py | 22 + gvsds/apps/gerp/role_scope.py | 2 +- .../__pycache__/form_api.cpython-312.pyc | Bin 18847 -> 19715 bytes .../views/__pycache__/menu.cpython-312.pyc | Bin 18959 -> 19065 bytes .../__pycache__/order_flow.cpython-312.pyc | Bin 11144 -> 11016 bytes .../role_permission.cpython-312.pyc | Bin 10833 -> 10774 bytes .../__pycache__/tenant_review.cpython-312.pyc | Bin 8741 -> 9165 bytes .../__pycache__/viewsets.cpython-312.pyc | Bin 45384 -> 50402 bytes gvsds/apps/gerp/views/form_api.py | 74 ++-- gvsds/apps/gerp/views/menu.py | 12 +- gvsds/apps/gerp/views/order_flow.py | 6 +- gvsds/apps/gerp/views/role_permission.py | 8 +- gvsds/apps/gerp/views/tenant_review.py | 33 +- gvsds/apps/gerp/views/viewsets.py | 158 ++++++- .../ugm/__pycache__/views.cpython-312.pyc | Bin 25190 -> 25101 bytes gvsds/apps/ugm/views.py | 5 +- gvsds/auth.py | 82 ++++ .../__pycache__/model_utils.cpython-312.pyc | Bin 4152 -> 4335 bytes gvsdsdk/auth/__init__.py | 16 +- .../auth/__pycache__/__init__.cpython-312.pyc | Bin 1798 -> 1967 bytes .../__pycache__/admin_service.cpython-312.pyc | Bin 3369 -> 5518 bytes .../__pycache__/middleware.cpython-312.pyc | Bin 2864 -> 3677 bytes .../__pycache__/permissions.cpython-312.pyc | Bin 10118 -> 12995 bytes gvsdsdk/auth/admin_service.py | 133 +++--- gvsdsdk/auth/middleware.py | 15 +- gvsdsdk/auth/permissions.py | 99 ++++- gvsdsdk/model_utils.py | 4 + 44 files changed, 1208 insertions(+), 568 deletions(-) create mode 100644 gvsds/apps/gerp/migrations/0013_workgroup_role.py create mode 100644 gvsds/apps/gerp/migrations/__pycache__/0013_workgroup_role.cpython-312.pyc diff --git a/TempLate/1 b/TempLate/1 index 46d3028c..88f55ddb 100644 --- a/TempLate/1 +++ b/TempLate/1 @@ -1,5 +1,291 @@ +
+
+ +
+
+ + +
+
+ + Studio Report + + + 2025-10-25 + + + 已归档 + +
+

+ 银河锋锐软件开发工作室 + 第六次全体会议 + 纪要 +

+

+ 本次会议围绕 15 项关键议题 展开深度讨论,全面梳理工作室现状、部门制改革方向与长期发展战略,由工作室负责人 TermiNexus 主持并发表重要讲话。 +

+
+ + +
+ + +
+ +
+
+
+
+ +
+

媒体文件不可用

+

录像文件已损坏或丢失,无法播放

+
+
+
+
+
+

会议全程录像

+

原始录制:2025-10-25 · 16:07

+
+ + 文件丢失 + +
+
+ + +
+
+ TermiNexus +
+
+

主持 & 主讲

+

TermiNexus

+

工作室负责人 · Studio Director

+
+
+

会议编号

+

#GT2025(GX-006)

+
+
+ + +
+
+ + 会议关键指标 +
+
+
+

15

+

议题数量

+
+
+

4

+

核心项目

+
+
+

6

+

部门覆盖

+
+
+
+ + + +
+ + +
+ + +
+
+ 核心议题摘要 +
+
+ + + +
+
+
+
+
+

成员发展与关怀

+ 01 +
+

重点关注九年级成员发展,强调平衡学业、生活与工作室事务;关注成员身心健康,提醒做好冬季健康防护,体现以人为本的管理理念。

+
+ 人员管理 + 成员关怀 +
+
+
+
+ + +
+
+
+
+
+

组织架构改革 · 金字塔制 → 部门制

+ 02 +
+

推进从金字塔式组委制到扁平化部门制的核心变革,明确各部门分工与职能边界。识别当前痛点:人手短缺、新部门建设滞后、执行效率不足,提出重启人才计划作为核心解决方案。

+
+ 组织变革 + 架构优化 + 人才计划 +
+
+
+
+ + +
+
+
+
+
+

部门重建与核心职能界定

+ 03 +
+

系统梳理现有部门构成及缺岗情况,表彰工程部、美工部核心成员的突出贡献。明确各部门核心职能定位与跨部门协作规范,为后续规模化扩张奠定制度基础。

+
+ 部门建设 + 协作规范 +
+
+
+
+ + +
+
+
+
+
+

品牌建设与长期战略规划

+ 04 +
+

探讨工作室名称定位、商标体系与企业文化内核建设;明确项目推进分工、质量保障标准及测试机制,系统优化 GERP 任务追踪、工作复盘与成员考核机制,提出新项目精细化推进与弹性时间管理方案。

+
+ 品牌战略 + 质量管理 + GERP 优化 +
+
+
+
+ + +
+
+ +
+
+ +
+
+ + +
+ + +
+
+
+ +
+

+ 部门制核心是分工明确,更是心往一处想 —— 工程部铸骨,美工部赋魂。 +

+

团队的价值不止于项目成果,更在于让每个成员在成长中遇见更好的自己。

+
+
+ +
+
+

TermiNexus

+

六会金句 · 2025-10-25

+
+
+
+
+ + +
+
+
+ +
+

+ 和而不同、创新为主、以人为本 +

+

+ Wishing you eternal happiness and joy, and thank you for bringing vitality to the organization. +

+
+
+ +
+
+

银河锋锐软件开发工作室

+

组织寄语 · 2025-10-25

+
+
+
+
+ +
+ + +
+
+

本次会议已形成正式文件

+

包含完整决议事项、部门任务分配及后续跟进计划

+
+ +
+ +
+
+ + + + diff --git a/TempLate/GERP/pages/Company/company-detail.html b/TempLate/GERP/pages/Company/company-detail.html index 4706b47c..f034388d 100644 --- a/TempLate/GERP/pages/Company/company-detail.html +++ b/TempLate/GERP/pages/Company/company-detail.html @@ -152,8 +152,8 @@ hoverable: true, bordered: true, api: { - get: buildApiUrl('/API/FormAPI/Get/Department'), - add: buildApiUrl('/API/FormAPI/Add/Department'), + get: buildApiUrl('/API/FormAPI/Get/Department', { CompanyUUID: companyUUID }), + add: buildApiUrl('/API/FormAPI/Add/Department', { CompanyUUID: companyUUID }), edit: buildApiUrl('/API/FormAPI/Modify/Department/'), modify: buildApiUrl('/API/FormAPI/ModifyItem/Department/'), delete: buildApiUrl('/API/FormAPI/Delete/Department/'), @@ -188,8 +188,8 @@ hoverable: true, bordered: true, api: { - get: buildApiUrl('/API/FormAPI/Get/Workgroup'), - add: buildApiUrl('/API/FormAPI/Add/Workgroup'), + get: buildApiUrl('/API/FormAPI/Get/Workgroup', { CompanyUUID: companyUUID }), + add: buildApiUrl('/API/FormAPI/Add/Workgroup', { CompanyUUID: companyUUID }), edit: buildApiUrl('/API/FormAPI/Modify/Workgroup/'), modify: buildApiUrl('/API/FormAPI/ModifyItem/Workgroup/'), delete: buildApiUrl('/API/FormAPI/Delete/Workgroup/'), @@ -229,8 +229,8 @@ hoverable: true, bordered: true, api: { - get: buildApiUrl('/API/FormAPI/Get/UserRole'), - add: buildApiUrl('/API/FormAPI/Add/UserRole'), + get: buildApiUrl('/API/FormAPI/Get/UserRole', { CompanyUUID: companyUUID }), + add: buildApiUrl('/API/FormAPI/Add/UserRole', { CompanyUUID: companyUUID }), edit: buildApiUrl('/API/FormAPI/Modify/UserRole/'), modify: buildApiUrl('/API/FormAPI/ModifyItem/UserRole/'), delete: buildApiUrl('/API/FormAPI/Delete/UserRole/'), diff --git a/TempLate/GERP/pages/Company/company.html b/TempLate/GERP/pages/Company/company.html index 34b1f222..992cc1ee 100644 --- a/TempLate/GERP/pages/Company/company.html +++ b/TempLate/GERP/pages/Company/company.html @@ -24,7 +24,7 @@ { field: 'CompanyName', title: '公司全称', width: 200, type: 'text' }, { field: 'CompanyShort', title: '公司简称', width: 120, type: 'text' }, { field: 'CompanyStatus', title: '状态', width: 70, type: 'select', options: [{value:0,label:'禁用'},{value:1,label:'启用'}] }, - { field: 'ParentCompanyUUID', title: '上级公司', width: 140, type: 'text' }, + CompanyUtils.epColumn('ParentCompanyUUID', '上级公司', 'Company', { editable: false }), { field: 'CreateTime', title: '创建时间', width: 160, type: 'datetime', editable: false }, ], onView: (row) => { diff --git a/TempLate/GERP/pages/Company/department.html b/TempLate/GERP/pages/Company/department.html index b05fcc61..3b5093f6 100644 --- a/TempLate/GERP/pages/Company/department.html +++ b/TempLate/GERP/pages/Company/department.html @@ -22,8 +22,8 @@ { field: 'DeptName', title: '部门名称', width: 160, type: 'text' }, { field: 'DeptSort', title: '排序', width: 80, type: 'number' }, { field: 'DeptStatus', title: '状态', width: 80, type: 'select', options: [{value:0,label:'禁用'},{value:1,label:'启用'}] }, - { field: 'CompanyUUID', title: '所属公司', width: 160, type: 'text', editable: false, hideInForm: true }, - { field: 'ParentDeptUUID', title: '上级部门', width: 160, type: 'text' }, + CompanyUtils.epColumn('CompanyUUID', '所属公司', 'Company', { editable: false, hideInForm: true }), + CompanyUtils.epColumn('ParentDeptUUID', '上级部门', 'Department'), { field: 'CreateTime', title: '创建时间', width: 160, type: 'datetime', editable: false }, ], onView: (row) => { diff --git a/TempLate/GERP/pages/Company/workgroup-detail.html b/TempLate/GERP/pages/Company/workgroup-detail.html index 0e4160d3..7e6f401e 100644 --- a/TempLate/GERP/pages/Company/workgroup-detail.html +++ b/TempLate/GERP/pages/Company/workgroup-detail.html @@ -66,18 +66,24 @@ currentData = data; CompanyUtils.setPageTitle(data.WorkgroupName || '工作组详情'); + // 解析 UUID 字段为名称 + const uuidMap = {}; + if (data.CompanyUUID) uuidMap.company = { table: 'Company', uuids: [data.CompanyUUID] }; + const resolved = await CompanyUtils.resolveUUIDs(uuidMap); + // 渲染基本信息 + 表格容器 containerEl.innerHTML = `

基本信息

工作组名称
${CompanyUtils.esc(data.WorkgroupName) || '-'}
-
工作组状态
${CompanyUtils.statusTag(data.WorkgroupStatus)}
-
创建时间
${CompanyUtils.formatTime(data.WorkgroupCreateTime)}
+
英文名称
${CompanyUtils.esc(data.WorkgroupEnglishName) || '-'}
+
所属公司
${CompanyUtils.esc(resolved.company?.[data.CompanyUUID] || data.CompanyUUID) || '-'}
+
工作组介绍
${CompanyUtils.esc(data.WorkgroupIntroduction) || '-'}
-

工作组成员

+

工作组角色

`; @@ -92,10 +98,9 @@ memberTable = new TableEngine({ container: '#member-table-container', height: '400px', - tableName: 'UserRole', + tableName: 'WorkgroupRole', columns: [ - { field: 'UserRoleUUID', title: '关联UUID', primaryKey: true, editable: false, hideInForm: true }, - CompanyUtils.epColumn('UserUUID', '用户名', 'User', { editable: false }), + { field: 'WorkgroupRoleUUID', title: '关联UUID', primaryKey: true, editable: false, hideInForm: true }, CompanyUtils.epColumn('RoleUUID', '角色名', 'Role'), { field: 'RoleType', title: '角色类型', type: 'select', options: [{ value: 0, label: '组员' }, { value: 1, label: '组长' }] }, { field: 'CreateTime', title: '创建时间', type: 'datetime', editable: false }, @@ -112,16 +117,16 @@ hoverable: true, bordered: true, api: { - get: buildApiUrl('/API/FormAPI/Get/UserRole', { WorkgroupUUID: workgroupUUID }), - add: buildApiUrl('/API/FormAPI/Add/UserRole', { WorkgroupUUID: workgroupUUID }), - edit: buildApiUrl('/API/FormAPI/Modify/UserRole/'), - modify: buildApiUrl('/API/FormAPI/ModifyItem/UserRole/'), - delete: buildApiUrl('/API/FormAPI/Delete/UserRole/'), + get: buildApiUrl('/API/FormAPI/Get/WorkgroupRole', { WorkgroupUUID: workgroupUUID }), + add: buildApiUrl('/API/FormAPI/Add/WorkgroupRole', { WorkgroupUUID: workgroupUUID }), + edit: buildApiUrl('/API/FormAPI/Modify/WorkgroupRole/'), + modify: buildApiUrl('/API/FormAPI/ModifyItem/WorkgroupRole/'), + delete: buildApiUrl('/API/FormAPI/Delete/WorkgroupRole/'), }, onView: (row) => { - let url = `/pages/Company/employee-detail.html?UserUUID=${encodeURIComponent(row.UserUUID || '')}`; + let url = `/pages/Company/role-detail.html?RoleUUID=${encodeURIComponent(row.RoleUUID || '')}`; if (currentServer !== 'gvsds-master') url += `&server=${encodeURIComponent(currentServer)}`; - GERP.Navigate(url, row.UserUUID_label || '员工详情'); + GERP.Navigate(url, row.RoleUUID_label || '角色详情'); }, }); } diff --git a/TempLate/GERP/pages/Company/workgroup.html b/TempLate/GERP/pages/Company/workgroup.html index 3669aa27..253204ce 100644 --- a/TempLate/GERP/pages/Company/workgroup.html +++ b/TempLate/GERP/pages/Company/workgroup.html @@ -21,6 +21,7 @@ { field: 'WorkgroupName', title: '工作组名称', width: 160, type: 'text' }, { field: 'WorkgroupEnglishName', title: '英文名称', width: 160, type: 'text' }, { field: 'WorkgroupIntroduction', title: '工作组介绍', width: 240, type: 'textarea' }, + CompanyUtils.epColumn('CompanyUUID', '所属公司', 'Company'), ], onView: (row) => { console.log('[workgroup] onView row:', row); diff --git a/TempLate/GERP/pages/system/userrole-debug.html b/TempLate/GERP/pages/system/userrole-debug.html index 0c944f20..865b662e 100644 --- a/TempLate/GERP/pages/system/userrole-debug.html +++ b/TempLate/GERP/pages/system/userrole-debug.html @@ -126,7 +126,8 @@ document.getElementById('ctx-role').textContent = d.current_role_name || d.current_role_uuid || '-'; document.getElementById('ctx-company').textContent = d.current_company_uuid || '-'; document.getElementById('ctx-tenant').textContent = d.current_tenant_uuid || '-'; - document.getElementById('ctx-scope').textContent = d.company_scope === false ? '已关闭(特权模式)' : '已开启(公司隔离)'; + document.getElementById('ctx-company-scope').textContent = d.company_scope === false ? '已关闭(特权模式)' : '已开启(公司隔离)'; + document.getElementById('ctx-tenant-scope').textContent = d.tenant_scope === false ? '已关闭(跨租户管理员)' : '已开启(租户隔离)'; } } catch (e) {} } diff --git a/TempLate/assets/img/Mascot/original/1.png b/TempLate/assets/img/Mascot/original/1.png index be9939a7276caab160b38c1b2d0789cc31691b00..e7cf8e705944b49118ea90c799615aa4a1f05456 100644 GIT binary patch literal 17242 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 z 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 diff --git a/TempLate/assets/js/main.js b/TempLate/assets/js/main.js index 015eaa03..53b0bb4e 100644 --- a/TempLate/assets/js/main.js +++ b/TempLate/assets/js/main.js @@ -43,12 +43,14 @@ window.addEventListener('load', function () { link.target = '_blank'; link.classList.add('nav-link', 'btn', 'btn-warning', 'zip'); link.style = "padding: 0;"; + const avatarWrap = document.createElement('div'); + avatarWrap.style.cssText = 'width:50px;height:50px;border-radius:50%;overflow:hidden;display:inline-block;'; const img = document.createElement('img'); img.id = 'UserImg'; - img.width = 50; - img.height = 50; + img.style.cssText = 'width:100%;height:100%;object-fit:cover;display:block;'; img.src = imageUrl; - link.appendChild(img); + avatarWrap.appendChild(img); + link.appendChild(avatarWrap); userDiv.appendChild(link); const userFrame = document.getElementById('UserFrame'); userFrame.innerHTML = ''; @@ -118,30 +120,3 @@ console.log("\n"); - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/TempLate/index.html b/TempLate/index.html index ebe2e2a8..a3d818bc 100644 --- a/TempLate/index.html +++ b/TempLate/index.html @@ -338,311 +338,6 @@ - - - - - - - - - - - - - - - - -

-
- -
-
- - -
-
- - Studio Report - - - 2025-10-25 - - - 已归档 - -
-

- 银河锋锐软件开发工作室 - 第六次全体会议 - 纪要 -

-

- 本次会议围绕 15 项关键议题 展开深度讨论,全面梳理工作室现状、部门制改革方向与长期发展战略,由工作室负责人 TermiNexus 主持并发表重要讲话。 -

-
- - -
- - -
- -
-
-
-
- -
-

媒体文件不可用

-

录像文件已损坏或丢失,无法播放

-
-
-
-
-
-

会议全程录像

-

原始录制:2025-10-25 · 16:07

-
- - 文件丢失 - -
-
- - -
-
- TermiNexus -
-
-

主持 & 主讲

-

TermiNexus

-

工作室负责人 · Studio Director

-
-
-

会议编号

-

#GT2025(GX-006)

-
-
- - -
-
- - 会议关键指标 -
-
-
-

15

-

议题数量

-
-
-

4

-

核心项目

-
-
-

6

-

部门覆盖

-
-
-
- - - -
- - -
- - -
-
- 核心议题摘要 -
-
- - - -
-
-
-
-
-

成员发展与关怀

- 01 -
-

重点关注九年级成员发展,强调平衡学业、生活与工作室事务;关注成员身心健康,提醒做好冬季健康防护,体现以人为本的管理理念。

-
- 人员管理 - 成员关怀 -
-
-
-
- - -
-
-
-
-
-

组织架构改革 · 金字塔制 → 部门制

- 02 -
-

推进从金字塔式组委制到扁平化部门制的核心变革,明确各部门分工与职能边界。识别当前痛点:人手短缺、新部门建设滞后、执行效率不足,提出重启人才计划作为核心解决方案。

-
- 组织变革 - 架构优化 - 人才计划 -
-
-
-
- - -
-
-
-
-
-

部门重建与核心职能界定

- 03 -
-

系统梳理现有部门构成及缺岗情况,表彰工程部、美工部核心成员的突出贡献。明确各部门核心职能定位与跨部门协作规范,为后续规模化扩张奠定制度基础。

-
- 部门建设 - 协作规范 -
-
-
-
- - -
-
-
-
-
-

品牌建设与长期战略规划

- 04 -
-

探讨工作室名称定位、商标体系与企业文化内核建设;明确项目推进分工、质量保障标准及测试机制,系统优化 GERP 任务追踪、工作复盘与成员考核机制,提出新项目精细化推进与弹性时间管理方案。

-
- 品牌战略 - 质量管理 - GERP 优化 -
-
-
-
- - -
-
- -
-
- -
-
- - -
- - -
-
-
- -
-

- 部门制核心是分工明确,更是心往一处想 —— 工程部铸骨,美工部赋魂。 -

-

团队的价值不止于项目成果,更在于让每个成员在成长中遇见更好的自己。

-
-
- -
-
-

TermiNexus

-

六会金句 · 2025-10-25

-
-
-
-
- - -
-
-
- -
-

- 和而不同、创新为主、以人为本 -

-

- Wishing you eternal happiness and joy, and thank you for bringing vitality to the organization. -

-
-
- -
-
-

银河锋锐软件开发工作室

-

组织寄语 · 2025-10-25

-
-
-
-
- -
- - -
-
-

本次会议已形成正式文件

-

包含完整决议事项、部门任务分配及后续跟进计划

-
- -
- -
-
- - - - - - -
diff --git a/gvsds/__pycache__/auth.cpython-312.pyc b/gvsds/__pycache__/auth.cpython-312.pyc index 33045e5c3f1da89e887f78dc021d5bfc5657df49..d946b8ed0c35b8f714c205dc0ed0677c52daca32 100644 GIT binary patch literal 6296 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 diff --git a/gvsds/apps/gerp/__pycache__/form_service.cpython-312.pyc b/gvsds/apps/gerp/__pycache__/form_service.cpython-312.pyc index 6edd544b43fade275476a7c6ac8ad52fe3d4781f..25df47a5acaa79dff674f68c813359915a9bb2bc 100644 GIT binary patch delta 21370 zcmeHvd3;mXmH&I%WyzLgNw#H4w!C72SHNsGi}B847Ml>v;s`$hHr{xWAtcWcWHsP| z$xVaFhRGz2DI{^~wxLPd(l(u;>sXllBox}xUonuG>C}*P+NAx=%sKZ-vT?{XGoShW z_j}-@yPkXQ^4|I0bI*I<`X|ZnzAKIUS!`?!1J@tlS=sv8pN_{Ru`eB;@$u^R{KkBi z;TTTQR?uG9SlC|FSkykdad!Kh#yK=y**3SmxUsl>UgNy>`Hl127c?$tU)Z>i=Be5i zwU;!Ow3jxPvW$drlY^|ej#Hmyu_1jmrpyOG!V?U~a*{TthlO8h8{01Fk+e&D zr0udE8OsPPIqfsaIN4dK(U&aikqZoQoif>a6kY=~LoY$W$s?WRwSs~i>@&%F6oQ1f zEGBYixml~=6z7!TBIJQ1X0kS2rR`A)N?sx;>lv}6q}4*o^hS{F^br#oouekfK4Stf zS8ytjO?@urtU_$CM@jZ%B>-nrET`dA=d{yUt9n#|ieD+nP=I@~l?lpL8(DY2L~bXW ze1iO3>{*$}8~O|qIpey|!Qfm!|IW?VPTlBz`w#E+e)Rm4K?T=iB4GPk!{oR~#;`y`^K3*WKaj@H%e1 z_}E7$`+s-f(U=$qT;necQSQFC$KN=8WAOY(2cEn2{ikl6`1Y;g$8J6|^oRHQZk~SQ z`uAVF@#eAX=f5tt_t9hDz45}K>*o(ZiYVInlTSpO7hA^$kpHbWUlkK>9C;Ng-Ak4~ zy!Tv;!-s|8%5&6u%1~a%c<(FYZ#)ORcQo+bZs`2@6C)p;czyhdbK}R(fArilH^25S zNH>1$&0B8|iHV{JH(od=3U&RRBjblp!(dTCBFyW%yWITr7OGsnHV?J|J-zwWlVbg7 z8`s|r_b$qMI@1O^w$r(y+~O!-KYtXY6xl`x3Yx2~T))_gM_T zJ^rNVgftw5974*Dk*Z3oXV%^FCilEQ4#3uJ!12 zTt9#0#v$yIIOY5jsQQq0>aurALaMcHzP-GYbBAIuRPS|pyFE@>NQ+%t>w_d53HtOB z%hG5uuyX48mGBcv*tyrkdGcLdU7q}%ZoVtO9k{i5ru8!}0S|7u^Am~){_rp&}-qhXQ!cF7Mmt*02Bpb0%4DDePtdVw)8WM1c z(~IAX*-6d3+vRmPiEE)rEX+e^`JIrBt$AjEayh_!oX*I7mfy&#{j%z7$*HGio}4+F zwQ?+ZRUmo2KY9JAEHN0L6o}98$7dW^c~u$L`|v>ZWo2eCAuW(F!=EstS2LktwEAPE zhe`vQ6u%}VXiNzjawg=AHuYmhs#W|+!6>x{-H+{@P%?7$Z55-^26e_>%{7fKQppFk z3<)Q!$F0O3Fl71-nIe;*(G)b9gNE9PSjtVQRUD|8FfsAAfFZ+g$Osr_`VBLKhEymB z4aU!c2IKE;P=^i12UQ6HRf=Dg5>RFNRarqx$|>tf>+|-2CD(7s4K6cVPHa8CHE2!^ zm~;H*oM6(d2{oh6{saV$nTUh#9a#A>3>niL)~Fa}P!~wy@8ewQ$gkoyu(4MdW(ySP zk%Q`yk{`@8B?xjMs+d6AC=O-|idH?j9cLtql8rH(baxKSXDn!};|->Y9!1Ajy@|*g zFG1l=5k)l$X_>m&G}< zY8GX!UX)euG{b$+?)JJuF+1JfrX4NrHqOIyP={{?5|SY^em4f?EnauKhp)sid0>V) zQwl&N6w~5q>F{`69nEgqMo8M`?x6QF-wY+my7-6E>8)aD>T-FSU2QE7xtn-*b0^R7 zPe6g^;m@;<{679jolc%|NYyLttq;cAPUIiYA2bd&kHydIRpQdu^)4Oooa#B*6G$!g zrxu^LTuNOYj5nW{aeT(1@~g3l1JV~ZT#n5+Cmkw3s~TSZ^*HjXaf>1qcuqKo(v;$S zUzJ*=l>EI?U1gO2eLM>k<%?c8On80VK+nXcqhxvdqK|4=N51axkX$FR|+xTVr~hylv~D?y&&V3bLCtGS4qQFTs2q2 z)za_^ZY8&hTg}zcDr>m4+&XSO&8g=axDDKWG`x|!pWDPW((q<(3%8ZqM#D|q1DuQ7 zPQ%SFXd34O_7eX|IXQ7A#QHes>_8 z+6vfD$zod``xT&8@}w=^u`GJStLj3cjt@z?c2_k}DDaSnoVU$Uh-!#jvz18A{92M? zFUX{~GF6m&p}TxHl9fns_45xRIZXE03l`{_T0Bh@4T)+j6x-a{-sS4p-{fiT>~e>+ zVI3#N)L5FTJMta7ec4*%a}-EOgal1{-F(ZA{rp!j{TPxXNcxaG17x}`B?&1Rir-_# zzLBLVm8_3yyMQLuuSp%0UDjj;)3SRjE^AYQj;!8_5484Rtl`+UL)!)_F2|-#=4H~n zYiUr1mWB9bt^Gq)LQrMuRUXqG(oQf;!b(ZdY#Epvu;%%#c|+bn!7_irva1W0Uzi&x z-{LRda(P=b`Dtn^yOk6>>SxzxGCzvVtSw>QUzoWfp7}|pzIK81C-YgPCHfUQ=}+TW zr1AO{+0vh8u`9AkXHO#WI~KDIM3_W zPMvH>83yWSBs?AA`^dv-=`Hxkq$3OX?*xWv9(|DFiU)W?!3j?w@{oDTt(700v{|6j zJgD~$FypFuS9A#j6+;z)+~xk<<(GBkqpEU#7i5yIo&?&F4bYJ!-#y_q0QYc@!btkA z4%J6@0#kaWt@TW%^DjU{(2*D6hd+e`*UVi#!&HxySu>?;{gCvKb$ZvR1mdbJoZ7*X zD?00-Y1kAfs`eLEU)I%(s%rRMM4Enj`5tUc#&^OG--U$UFX2==xQ{?;C`Qy>P3^8O z{vafHWIzxH@|($x^f{7ZmRK|D*j*#78Rb%T4|yZ|?|jpEdXT#pnJGcjB3KO>D(f0* z8#Xnqtu0?!xxQ(2*;-M%DggevJ2)Q2;Gx@0$c);&sj9YeRYg;{5|nnT#Q8@Nc^?v# zjeiwuXlL>6Hh`vF6NLk6cNai}jvXyK`E!u%Q2{~u@q#`maw;W@*^$*b&$9D=4S6RR zKshjk;w7+AXd@G39*H2WXIcP&!5{4YE0}(E>qlhe%w#PmwLp!Y7}#~o0pyrccSbQ) zk48{(a+6g{zmj8j_3+Ji|Xqa7cG8q(Duv&VVyxlJ!2j#EVdnTPGu3vp;~NETFx z!XUvR#P!6oOa}{~D3;F_V!>WvCPtOf$EiiU=QT!o#d0y6<}BVFaPe&42`}!C=yf*y z+oKa?70lL$Kz{v{ntDOSDIzs{41(bR+h?32rGblBB^cf&=vosQ!X1fc4a8tI&5jHk z%k-5@fs_WXE!=2yc%Vb8)Qq4bF0hIGRFOigkHsL1#3?KU=&8;l>1X>g1(LSMEP+7= zMP-TFuX^Hzc$#0X2J4m(PqogEPMaDyLklBlp9h20ixNTC!oI>DWqXW*5&C-o`fHlf zUy}gS6YZ~gN>9yGda4!7C|zL(E0}u{gajCryt|mhOkt8JBp!r6LEbtG<*if;F*_tJ z(XI;#r7#+T283~fFiCguNt(jPB3N+n1q*;;%4wxqNcuddzF9J+?@<}7%D#V*G2Xn$ zPzzw(62q`!C0k)B(rrX?k6uXj7LXY;(;cvo{|X|2!HQ$)QdL1{+M2daThkI+P)*mg zo*YOwk@R-cmtWAfdo5@+m^XW@!rkny&HNJgNA@JUDN`3WX{CgPmIt?z|4|cJGfTGW4VfDF?7{5iB0M*bUPbkCo=c! z+~MxZTtAIWAJq(sJH4UP!w^(8<_YekScK>E^BIP=@2*ZiY64k zEyOmRVVstbtgQuh?uaWW+_)F`;iG7UABJ)vsmJXNNnMQ`N15|qw3i5}8_Jfis-#e+ z0vo8o+A&a@bMJ6+678JrIO1euXH%cn<+oG5Hlv#WWRJ0zyK zAN2CBaEcO;OiRa(&dE`kHYg#rIJYp(6c2?ol*fNDlF%gus#}i_A z@F7Ll?j|T1Qo--`{a&ab7spQG-Vsu`chF3>C8Q8dE}@v+?)^<&yn9E>gP~a1649n; z`yn;(3G>iIT7kwr4${PBNC0&GC~vW^85X@~nH+)mM4VJ@y_#qps3twVHMbRvJ}GES z?yZ|pF}9RZRZ`Gu2YM~h@i8-BZSJiOS{#Emza_tSMKC#SFwdV{*t>E{$eMnt0pwEo&FCd-5p%G}m6=9EEaAidb1UK~hY;!j_4Vb-Pe)mLp9 zgSA6F=iQfW6+wN{Ckn=z@_ZJEmY8~nQOBnBORpx_2bu>L4SB~Bd~^GyL1V)4rB@SE z20Vk+mlJb?Ng1bfCv`*8H`Qm5qTyw7C&Dt}y z!_8;w0=dilxywGVl}+e?`|SkAlzK&qhIhjg~e+N`Fkyl5vO87`C(hWmoN) zgX@Mg!wqBhMg3J*3+4^)J-_dALG{4iQ^Iq?#ryhepo^o4GlEGD9D}mKhleZ1YzzA< zgNe4$#GK#*?$M;2K++t4(wyPV7iNvlts7nITR)amKiaUxpR_fYG84w$yqz78TusXv zG7K#rmW`zq4=94xG}xyGH@ik{xdGdJzis|`>xJ#33u-UU9<$Y5eDIR({@Y5dc*lT( zPHbRdd3?gNOrm2@HJ0G~m|38<9IEOq>-7Y6`e(`qlw{QPju*5MxWRlH_^^A zwycktVzMefCoPzgHQ4S?ncI&xXM0+|I%u(-vYfO8EO~xQ-k7C;oZ6A(iy6yb9?Z-d zP+v*1!-6oROvL0EEZ3lXRnYD@wdv%hfW63XFB-Gsnlh&kuve|=gGGZoKCtEur@fv1 zR`&T7W3#J%K7X{nVe~%N2P?M+v$9{?e0uYxtVP3RZ&$rlHBfcMmIkcN>2M3A=UhrF z9$M7D;#yYz9Y&JvyN?YzX9t|6erIXGS?PCHUbOn14R@rH?EBd3d2@$1U5LHdJ(jm& z)VTqS*5-7uP$y+whpsgbEgJTYrIZdxgVvORk}I~%t3?aW8_(~&TvRn!{#xzJwHM0= z)In?Js5KWdgO0+0W3JyZcX;=OlF{NdqwDI&91Q`D<9@&6{?Sd_#~jV%FZrpt!E}&I zYFWt+EDvTC2eKCWvlgB&xhNT3wDRKmv8=U$tOkEp!{~->V_8k4sKDWaNrTd#Xqg%L zL(RjB&U?qQD+V+{Yxbyh_SMW;Ln~pLxv|WZ1C_zl%+b`ss}4kKELB*xa$_bwmD}8#$#TLiH9Sv(_0@e(!hChF@U&`n$cNkV}g(uMAx(O+S{_;s3 z%Q#?-vX-=v%>ikaw7+~Zn=#oTDb^g)n;_3sch1syRf3fhCj6Fma7k@1ub!|wq zyrsj%@2AS2e+4UF$K==w&}|JZ?QWXkl<+r+VcxvTgP14j=nTcQyB=%;8~IM}E@wQ| zu2Yn)64~5Kxvs{H2T9*NE4!Zz%}WDwAm~jc-T$Lg000oG)j}~e+~nO4M^-nmO=(-_ zKK>4bJjgr@TtNG%=a;j87+F34id2R61E+%DL-I>zuI9fFF`h?4H8^YsL?Zle>tOGwY+ z48z@JdB}TfaV}d-e!ci)3_bqiw?SJYN0tQH5^8BiO9&r>1WgaT4hceCJ`+etR==yW zi`Qe^N(qSK)baDlOUv>Wm0${Xc$USh@DyM*k zA0_K*Hg3iF4XI&et)dHy|24){yn9b~3-9K{)hU{VB5=BSHw-du3m3D~B2IfH%F>Bs ziZP7HYn|+2Qc!F56=D`5LjE`s*dB&W%2B)+(zLa-w|EgJb-P2-E*L!&j(-aY#hfD; z!nFivzGz_!#n5F$u{e*b2zp5mvpgbvlLM^k+6}A2l7%a`EDQJ)0!1 z$@KjOvvRSs*D&;3B>xx5?~vea&yORy2_&T4-U$c5t`7d6FzF*C`!QXO4+fDdJ`MpY zM;;O(nHPf27&=d(*fkyQhE5=~Y)H4-Mc!G{UPH$^2V{V|FFM+yxsqn#)PdQQuBP83 zeh-JwGQ^$NdMXrmfb~uzRn`4(~-img~YMGij|T*r@@m3w)|)8 zq<>=^t0TW%Z)Nr5`==}l1$YIq0(rnzJ_)$1QQS4>-pOe$|31ngY zdN}w1&zvZ)+Z!^Xe84D}6gA+!+OPpHMUcT!%t7!i`ptc2 zV@!05FY9MJo|u}a*W8u2lUm2Xn8P;;ET^T-sW~|Q-3!kqQC7%XMT#~ikb@gC70H_)`S0pu`K@lSlXtxK)!RIRss%Q2P(}ZVPl-m+@8=AgAQ>*i~wh2kZvOHZivzL z$l#dM^mi=SZljZVkAg~sYnzQr zu|TV6JQI@PW@7tdCkOrZ2D@^_q|hKJjHkvvx$>s3HG^Pnt;RyB_K1`$sF*%Dx9IBz z1CN#0!EDP~Yp~2x>rti$4EsV7{7XP9Rx;C*;>G77y@W~Y7Z~(=96~Bc{S=6k1_uD? z5h+&@%~pFNK5VC5JsE;LBH!}xylcUpS2wL3M0+y4>msQ_nvlUUQA>2DkSU}?Ej>B2 z)hcyB?_jNH>Z7I8$@tb(b_4dukP6m0N3^Cxa1iIVm6`>Svff3+x6K@0Th>`Wi`byR zhBc*3r1TC6*t?SG-610r3KPj{)Z3`>>yP)WxQygAC0ZnX_AkiY+LSm;(r1~HiwJ~X z&`?fK7M#K}ZQwNt6Qpdt4}C@Csqoy*B0p#{FK}!E-`Ae3N3!~Y;_p5-!pWH-nu7px zz7Om|a&M>C)h2?oV)DcTYin?2e`EpzCtg^D-vsI;Bu84CFITn8|GFaAC|gbLFvS)rJE+b-asQ!logA$XOT^=w8YKWr~(W) z?JfW`U_aT1z93u3ahGWoRm3hRz#l^P621z<)mTFV_AG#XUKhBV@Y^wY3y>u;Wb8t+ z5D5iY6mFd%|LQ6);@?F+xl_)>5md*|^r|sd(u<&vubkfzh(waQD$mq(GgWnME<$r|SRPG0x+qyY7 z7gFum-PEywigFQiBs)6zA3}itF%r6Ne}?H&zH=X}BM}cihAFJulMTkTY&qaOKzL7a zSxT7{HY86d$>J3@@{e4)ERM;^Ba3&=v}?=*4FUUNzkTr~&Eji1V?dX7NtZU*KK!sh zdpSAkesS^B%fbASOf4ViBv9ios2Q+~S!y9Yj)_kQ7}CcK=|eHYyDsG{xsW@$eye}^ z)=^i-DBp9*)k9YASnLZL?Sn~|jLxAg=QsQFYOZC>=wB7Ir=QwrHW0L5WDaQqIZOOGO9DC7{+#NIrI&Ix2JNY6Y#D9{6tDIduO8Sm zX0JObxdy2|OwJ6TJVP}y&EmrjDu-F4h^ZZHkhP%g- z7WdX((IthfoBLH@+60oC0|r7vdVkci*}puPXc@Tw_#@T0JXNh7oNC=FWiFRkw_2GG63d)h=P&_QhtyC$4}Lz3i`$$k{V;_^nwz~PL4MWD zBDG{}W#m_vvOs?&HEd0i{wk40YE3b0EtLMMfJHh-y)9GvtJ0KhsnTDk$|2k=K@We) z4fV(aZB~iY3NH5B$`i;N9cp5F!ANd5;8A1@xz(Ywra?F81t+`_Cw)9f*9%&z*L3wt zlGhnW>%DuwN!1f4#PJz$R)|XPkxnCtw;Dljb1K}(cO3K1Aj6$`Q(6&E@U`B*)y^ZG z3bLZB8rj7FyBNw&M|9Q%j8g6*Hf00kKTape_@9ZLlI>;r?-Iic3IE+*>0*1doJNQR zRP`_@O*meR^7yhczqChTqCN-O9zz)88+a$2RN|WthxeG33W`bfAaJ5u&S}Gaf|ydy zX)s0`g<+NOj!2OFYo!T}IYA^ix91y#ctH+bTpP{i_v9Jr?t)L7JjHI!2V9*@es{kq z1>9caku~@YNVqr=6mvRY0#4BYxe9rcgPYuC7y(a6IV(5<$m#LFNiek*BU~asQKKE?&tlK)+db5#kQZ)Y6eA46s{TiS=}*NoWaat1(S2;gSRs#MWTU5-oAgGWp&G zmn@jbp8HI3e1aDZLG%)n$fu9RlU7g6l4j6(&5|DNK0JrSKGF;D(PKU4DV59-33?K| zWsy>?%gOD-CX+RU9C@AA7Jo)sIcx3)x?iliiEVSWtWm35SNKC~OA#b@y)-9hmbe`SIQbYzO)G zy{SI_dr$(*Phhm+(PJ1+v_h&5_r5011u0KraSFL|#Ir?eOne!cQrvSDL)5LxBiIvP0+3^YgEfs7(fA~zaw9gPn1~vHLz=K#Kh1j-Yf^jx1~pG-+g>oJp_K(3qCHJJc8ApH z%O#?agIIJAIrE@Z0k(AL75VXlX(@!@Dl-9Q@dwph;63_m>vrN78{-hrtP1h z#664WtZZb>eiduT9MfbDC7+)gDB0{U*?h^lne2J!uW4`;qSXg9wo4k@pec~H@KV~s z^R1(68v<*){P2HC7nCYcSO*^V>t>R}J2s}ynID+5(m!Wq|H?t#B~#(G!nuLM8h>F8 z#I=4?-bnkyHdbFPVXT`$^UcMdKYucp5aDz+4&M6<4C|6*KaPn5`jeRkFunhzG!Cut z$z_gpnT$VEU0*2m=arf2XGzCc79wNH6bxt4@GN7T=a4gmWfiUV- z8jdbWOn8QrKay|N$Y3OJGkJiyuF4#280Ow?d8_6BsFs1>-b;rp60Mp%(B>vu-`k)x z1Z4{g1}6zQxZWaVc^4^KpGYRoSo15GW9nwfE_jrHHU=xsly}+G@U9!)32;#zsBTrE zkxLc>wjMNbN%<$k=B&hT{;InXIn zQMEQjIB@?83x`%Qcy$iUabfAg3Gt$m7WE+YG?UW({T~jSV~wC+R8+rc=zJte_y&~U zp7I6q$3r@`r^H2#cpi2?6P|+M2IR{GyHp<3*gnw&tOs$_Bn(@Q|b~U^;LCgO@yk5!;7V3Xm1ae^jd${r%EF(c_ZZ za(D)L_#mu2i&@JX1p}vnDmt;BaKra;;477O#!PIFsa4=e1xO*BkXIeE8gx8(uN|2* zcL{p$?8szBmLhNT;**+Aw*!pyy(#kw{P8I-KZ?hZ5l>3>}E*=b0dvj?Q3~ zpa=Y>X`PSV(M7p|RB^2fQSlj~=m1PZe-Pp)S7?>E2+&6m-t>%b1&i7Ai~47oyiseJ zpaI3Nk62y8jobwSSait)-zb2U@gUYxDpuU2wG-;l871wHDOoz5y6ANFm|H!OOi;L4(eB2rA0OMDUoRNeC+9!hk}JfC4O5{(2M1c--hS)lnr& z^{=SBsD3E{4x&n>`Y@_Y!Qu8Xe6Bx^1eK7ej8RYXPavWCuNgAn@kW`@s#H(oOo_IT z!%z}TCb8XJr2p{*-w}w0RB-Ags?a{INQ_nw( zqP>iazEGJT#UkIpBB;khO7Q$a^~fK?^sgf6hID6w`0zgg2|j9=5S1_O;7eHQ1t8Q3 zM?6PTv~_mw?iPKo`8`+wm1;U&YKvQ2ISL?mtL~XxfW635HI!H3c=yxb}TUTP3Wl| zBIA-FV@MXrDZ7+YcA{x!~JtUZ45}3c%KY#5&)o8|?OSZY!<}L`# zUG1N{8bny^w-uq!N30KIK&PdoV0=b@)``61dEyI<{`kDX9YA`^5v(Lc>@0rGVt6Al zY-iyD*-I~FFTE%W)VTaLuF>u8%QZWKiB?+Tt|8?DRonbk+eVwVU#@C~64w@%U+@O1 z@Ap@eq^IVwHnQfantU)V=uH6~t$6=vQJp`lZnS>usJrD)46}o+lH88|G(e2}bpJ;3 z&rj`EEVQX}Z!aQSdRxmsy;Dmu(4Qw}GI}#e>DQqXgF5MoF%kVAl0NgKz%u$_sbO8N z>_>5NKrc#c>xvkEk#4`{&x_L z^h+Qdsi+U{LO6z~N(>_$sxtBTRV%7H8%>V|JfOi|!a411?<`nq^djt6&>*BD1%2{L zO>2H6ee$;_ZVE!ta9U0hQR$*uu!3F;lh>2On2}m5x{Bw?_|< z%OJouZ0kD!nB%<*BE@A0D#&l|Giu@@gtB>$ z-=N(3NeN_v@pCLG#)zFF8DI!#!~n(*&JboX2&a4Hh%BU7KkaB58|@TWz$QBd=7WPr z?Q#R3KiQ2q!2l>E+-oTC+oN{2D$>X4wK22izsg+kMVZj9R72ahk)nPhOKmLxgp2@# zV$Yc-O%MCc0IVv>p8iy^OvXLS+ye~x`!njA!fzBOP60(#Ujh}0s0Ply8Gas2K97fJ z@HvyraHvACEa0bcOkvxl=ZXTYsU`26xJKI~D`S~at zJtlb%L-bgR9^+^+x)4b%kdQKb93dXQP(br7WCp7a++L8(M1qLMX`Tc#)38hy9|be~ zIjsIVl7mNYm-v<;Fvl?hdcV47dcgPn*f#Kb@!Ci5lZ^#3gxT z2C4f0xa3RV63hP{F0uVhTw=Qmmn4pSLSiMpF9H>oD@OE;R9im#Q?fPIKEVL_%!9^I zz!wcx#1}vTUo<$FpCv9=H55yK;V>edTNVpH|0t<2!_Q@hu3@(HauJKPSl+NidDoW{ zl;q7rs*&zzA7kSb(L-WocNR+&15X!y@5Ni+de(6d(2p;vj=%Ek_*;)UMAQxMM?7`o z-B-xqfZIoRAGoP$5Uq<%3eHBst^yx$$^0+A07XDjWfO@ zeD@Yj+@Zl6w2?3V;Mh!_u{-bsJ&%3&#=x7v?xSbkKnBs*QlN@x7sl;q1CRUJx+-uS zo&I_%oi#dFbPGu*Omy?4Tdm25RwxGNO6=2AS5II}AH5Hy(wP!p=Fh*!`uazlzTOxp zkMpOb^H<=r4wm?J`GD`Q!fOr2!A@||wv=A7Jn+8qqV(dH(alX`%N~F?9pFS2t)71P zZ(KO4UHIu8HDlcbCd-(x$8vNXeCEl8CLXnw6*Dgx$_iz}aX`MOfank8Gm#d`kru0$ zv(g_j5_b8>iRTtdeC^ne4j|y2NEbG^#)mZq?r25hHMKDB!zk`%r#OQt`4 zYOJ5^FCCIDl$}FD#aW9XZ2z94k&PJ@NLC=B7WyL?!Xe=aj&5BGwyzT(57ZP-4J=16 z4-o`UZ6e>s(0Z&?kK~s~4kDqd^oN-HK9YBy{#ue^GiH31q?#+G@}xgLVj<8pO}AbYVpo5PYSn4c?e)+PyENghL2; zk;+4FOHV9R!lyy>0=)3Mv$G^w@)7;HYF3B+t#iui;Br>;Ub5r$6a{t2$sw;NXC8`2#SO6H9d=QYBsupAslo1Wdcz# zfDanOBY7MCJSX6B{x~xsnPE@_RdM~Y2`PlGsZ9N?6LJWFi9l`W&z?|XSjDIl`X8Q9 zV>pIU$M?^l&|p~0sLfD57Q=BqMy&%jI*jNUwXwfu!hqp;Mr|3$pD<$BBvvqEIDt`H z;T@fcM2sXc>a5Z1WfK;RB#X7J7`BOFJBCvjwW)v8L@I_IVryv_PNyRIZf9U5Q>>7M z;cP~oG+>{Yf#Do6-HG9uBBNOt&K1-1Fq|)nSAgL{Mx8jIohZWaY_SV-Fg#bRTa4j( zBLDdqUI6{4{aJ{yMdALGV5F2$>-)_Ui!r=JtiBY(%UH3EG8SXYA%?}uX$;l^cJ_89 zje%cRVp`CW9kk~MO}0C$G4Vj{L<7qtXAWpbW#&5yhWuvOo>eMeF1aJHQh5b?Cq^o- z1*(uI-_d8v6Yi{HD<$&8J9V;=M!AZ(CZ?qq9#BWJ_p={&ux5FpxU}f1`Vdx>vqF5b)8~0VuD@rP)r zcyZa$2iJCb6YUyl&!@M|U0*MQFC4ieUoR8yIIJda9mv0rB%5(lPQPX|^;D~9lXR1>8Vp?~bdkKF7B!>Tt=WFEYZ#J^G$*phB dv)`$a%xCo91$4*_Q3(!2RRi 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 k!kUIjKM#`NSZ~*V0QdeI=>Px# delta 1333 zcmb7@e@vBS6vyv*xi83T+_1(SGzB6Vf_iUUNQ?-^kCHpZ0J)}MUEXnDAnz~sycdba zg@86KMn61jE2t~N3>8XlkJDybEvvOwb1QS&)lo(mF>?OXW?I^F-sV<+cDMJl^PclP z=Q;1W&-rvtI(n8a`X=jz>m48LYcg)PyXRZ^k{JPu!mKeG|tQg%F;y5n*S z_{&m40;KVSiLeDaal&;SQu$lkgTNb~+9g3co^%(=tHES?9Iv@AE9t^wgkrqd{EnF{ zw1=#GRP7@1y`FB!#9`07V8=%^n_M8`O&HMn91l{WLo}re!<5O^3mS!?$hK?vgM|(-U zkIZ5Wwby1F2N%(48^~Eg1uaHFN~#R{f{f+iukGp@61!WV72|E7Z4~g(hUmEuZZkprsDU!tl`m7{B zE$j%^?TLX0*L7{JY8LOYqt;f*hXj|xClE4I|+@L5zdD! ztP9sbKOP9%9jRVOrjNSp8{EDdgHI8k!;zHPta|5}ekEFpN(Z`#EYn5T@ zgYYq)8N6gB3qO&q{IeI%NU($B-h7ZF!a0v$?&BgS{NkuGLOF!9JnN;6U?$T@3Jp0D z;C`IkzZ0SeSSX(mk-M=Nj{)b4I1c9HB>iSv%JQf%7-0$c%-#ulIypFFrX>nGCB z3B;5Jm9=Rhx)-rTXKlI1!!%uIdHcsJl^CfA5Ad4t7JwL!PMkKIFA5JXMI8Ld;d?;3 z1bFR*OotvI@FMT$rQz0=W xH#5(wxU?X(Na0z>G$_9$H7_x*Wb;ABr%XH=E>i?##+Gh7)9 diff --git a/gvsds/apps/gerp/form_service.py b/gvsds/apps/gerp/form_service.py index 7b7b7b42..5e7c875e 100644 --- a/gvsds/apps/gerp/form_service.py +++ b/gvsds/apps/gerp/form_service.py @@ -12,7 +12,7 @@ from gvsds.apps.gerp.models import ( Tenant, CompanyInfo, Company, Department, Role, UserRole, RolePermission, Permission, Menu, UserABAC, ItemReference, Backpack, WorkOrder, Message, FileAsset, Password, UserIDCard, PersonalPage, - DepartmentSMTM, Workgroup, SimulatedTrigger, + DepartmentSMTM, Workgroup, WorkgroupRole, SimulatedTrigger, CashReceiptsPaymentsLedger, Asset, Liability, UserSettings, PositionTemplate, PositionTemplatePermission, Position, UserDept, AuditLog, @@ -94,6 +94,64 @@ def create_default_role_for_user(user, admin_user=None): pass +def create_tenant_admin_role(tenant): + """为租户(集团)创建"集团超级管理员"角色并分配 admin:tenant 权限。 + + 在租户审核通过时调用,确保每个集团都有一个租户内管理员角色。 + 该角色拥有 admin:tenant 权限: + - IsAdmin 判定为 True(关闭公司隔离,可跨公司管理本租户事务) + - IsSuperAdmin 判定为 False(保留租户隔离,仅管理本租户) + - IsTenantAdmin 判定为 True + + 与跨租户管理员(GERP 测试员,持有 admin:super)形成两级管理员体系。 + + Args: + tenant: Tenant 模型实例(需有 TenantUUID 属性) + + Returns: + Role 实例(已创建或已存在) + """ + import uuid as _uuid + from gvsds.apps.gerp.models import Role, Permission, RolePermission + + SYSTEM_COMPANY_UUID = _uuid.UUID('00000000-0000-0000-0000-000000000001').bytes + tenant_uuid = tenant.TenantUUID + if hasattr(tenant_uuid, 'bytes'): + tenant_uuid = tenant_uuid.bytes + + # 查找或创建"集团超级管理员"角色 + role = Role.objects.filter( + TenantUUID=tenant_uuid, + RoleName='集团超级管理员', + ).first() + if role: + return role + + role = Role.objects.create( + RoleUUID=_uuid.uuid4().bytes, + TenantUUID=tenant_uuid, + RoleName='集团超级管理员', + RoleStatus=1, + AssignScope=0, + RoleDesc='集团超级管理员,管理本租户下所有事务', + ) + + # 分配 admin:tenant 权限(租户内管理员:关闭公司隔离,保留租户隔离) + admin_perm = Permission.objects.filter( + PermCode='admin:tenant', + PermStatus=1, + ).first() + if admin_perm: + RolePermission.objects.create( + RolePermUUID=_uuid.uuid4().bytes, + RoleUUID=role.RoleUUID, + PermUUID=admin_perm.PermUUID, + CompanyUUID=SYSTEM_COMPANY_UUID, + ) + + return role + + def _pascalize_record(record, model=None): result = {} column_map = {} @@ -105,7 +163,11 @@ def _pascalize_record(record, model=None): column_map[f.attname] = f.db_column for key, val in record.items(): out_key = column_map.get(key, key) - if isinstance(val, bytes) and len(val) == 16: + # UUIDObj 处理:gvsdsdk UUIDField 返回 UUIDObj 对象(非 bytes), + # 需转为字符串以便 JSON 序列化,否则前端收不到数据 + if val is not None and val.__class__.__name__ == 'UUIDObj': + val = val.str + elif isinstance(val, bytes) and len(val) == 16: try: val = str(uuid.UUID(bytes=val)) except ValueError: @@ -146,6 +208,7 @@ class FormService: 'PersonalPage': PersonalPage, 'DepartmentSMTM': DepartmentSMTM, 'Workgroup': Workgroup, + 'WorkgroupRole': WorkgroupRole, 'SimulatedTrigger': SimulatedTrigger, 'CashReceiptsPaymentsLedger': CashReceiptsPaymentsLedger, 'Asset': Asset, @@ -216,22 +279,33 @@ class FormService: self.current_tenant_uuid = current_tenant_uuid else: self.current_tenant_uuid = current_tenant_uuid - # 强公司隔离:默认启用,仅当 admin_verify=True 且用户是管理员时才关闭 - if admin_verify and self._is_admin(user): + # 公司隔离:管理员(admin:super 或 admin:tenant)自动关闭,普通用户启用 + # 租户隔离:仅跨租户管理员(admin:super)关闭,租户内管理员(admin:tenant)和普通用户启用 + if self._is_super_admin(user): self.company_scope = False + self.tenant_scope = False + elif self._is_admin(user): + self.company_scope = False + self.tenant_scope = True else: self.company_scope = True + self.tenant_scope = True import logging - logging.info('[FormService] __init__: user=%s, company_scope=%s, current_company_uuid=%s, current_tenant_uuid=%s', - user, self.company_scope, + logging.info('[FormService] __init__: user=%s, company_scope=%s, tenant_scope=%s, current_company_uuid=%s, current_tenant_uuid=%s', + user, self.company_scope, self.tenant_scope, self.current_company_uuid.hex() if isinstance(self.current_company_uuid, bytes) else self.current_company_uuid, self.current_tenant_uuid.hex() if isinstance(self.current_tenant_uuid, bytes) else self.current_tenant_uuid) def _is_admin(self, user): - """判断用户是否为管理员(委托给 gvsdsdk.auth.admin_service.IsAdmin)""" + """判断用户是否为管理员(admin:super 或 admin:tenant,委托给 IsAdmin)""" from gvsdsdk.auth.admin_service import IsAdmin return IsAdmin(user=user, role_uuid=self.current_role_uuid) + def _is_super_admin(self, user): + """判断用户是否为跨租户管理员(仅 admin:super,委托给 IsSuperAdmin)""" + from gvsdsdk.auth.admin_service import IsSuperAdmin + return IsSuperAdmin(user=user, role_uuid=self.current_role_uuid) + def _build_field_map(self, model): pascal_map = {} for f in model._meta.get_fields(): @@ -279,11 +353,81 @@ class FormService: return result def _enrich_entity_labels(self, page_qs, ep_fields, records): - """为 records 中的 entityPicker 字段添加 _label 后缀字段,以及 extraFields""" + """为 records 中的 entityPicker 字段添加 _label 后缀字段,以及 extraFields + + 支持两种字段类型: + - ForeignKey:getattr(obj, field_name) 返回关联模型实例,直接取 label_field + - UUIDField:getattr(obj, field_name) 返回 UUIDObj,需批量查询关联表获取名称 + """ try: obj_list = list(page_qs) except Exception: return records + if not obj_list: + return records + + # 预构建 UUIDField 类型的 UUID → label 映射(批量查询优化性能) + # key: (entity_name, label_field), value: {uuid_bytes: label} + uuid_label_cache = {} + uuid_extra_cache = {} # key: (entity_name, extra_field), value: {uuid_bytes: value} + + for ep_info in ep_fields: + field_name = ep_info['field_name'] + entity_name = ep_info['entity_name'] + label_field = ep_info['label_field'] + extra_fields = ep_info.get('extra_fields', []) + value_field = ep_info['value_field'] + + # 判断字段类型 + field_obj = None + for f in obj_list[0]._meta.get_fields(): + if hasattr(f, 'name') and f.name == field_name: + field_obj = f + break + + # 仅对 UUIDField(非主键)类型批量查询;ForeignKey 通过 getattr 直接获取 + if not (field_obj and field_obj.__class__.__name__ == 'UUIDField' + and not getattr(field_obj, 'primary_key', False)): + continue + + # 收集所有 UUID 值(转为 bytes) + uuid_values = set() + for obj in obj_list: + val = getattr(obj, field_name, None) + if val is not None: + if hasattr(val, 'bytes'): + uuid_values.add(val.bytes) + elif isinstance(val, str): + try: + uuid_values.add(uuid.UUID(val).bytes) + except ValueError: + pass + + if not uuid_values: + continue + + related_model = self.TABLE_MAP.get(entity_name) + if not related_model: + continue + + # 批量查询关联表 + try: + related_qs = related_model.objects.filter(**{f'{value_field}__in': uuid_values}) + for r in related_qs: + pk_val = getattr(r, value_field, None) + pk_bytes = pk_val.bytes if hasattr(pk_val, 'bytes') else pk_val + if pk_bytes is None: + continue + label = getattr(r, label_field, None) + if label is not None: + uuid_label_cache.setdefault((entity_name, label_field), {})[pk_bytes] = label + for ef in extra_fields: + ef_val = getattr(r, ef, None) + if ef_val is not None: + uuid_extra_cache.setdefault((entity_name, ef), {})[pk_bytes] = ef_val + except Exception: + pass + for i, obj in enumerate(obj_list): if i >= len(records): break @@ -291,25 +435,54 @@ class FormService: field_name = ep_info['field_name'] label_field = ep_info['label_field'] extra_fields = ep_info.get('extra_fields', []) + entity_name = ep_info['entity_name'] + + # 判断字段类型 + field_obj = None + for f in obj._meta.get_fields(): + if hasattr(f, 'name') and f.name == field_name: + field_obj = f + break + try: - related_obj = getattr(obj, field_name, None) - if related_obj is not None: - # 获取 db_column 作为 key 前缀 - db_col = None - for f in obj._meta.get_fields(): - if hasattr(f, 'name') and f.name == field_name and hasattr(f, 'db_column'): - db_col = f.db_column or f.name - break - key_prefix = db_col or field_name - # label 字段 - label = getattr(related_obj, label_field, None) + # 获取 db_column 作为 key 前缀 + db_col = None + for f in obj._meta.get_fields(): + if hasattr(f, 'name') and f.name == field_name and hasattr(f, 'db_column'): + db_col = f.db_column or f.name + break + key_prefix = db_col or field_name + + is_uuid_field = (field_obj + and field_obj.__class__.__name__ == 'UUIDField' + and not getattr(field_obj, 'primary_key', False)) + + if is_uuid_field: + # UUIDField 类型:从缓存查找 + val = getattr(obj, field_name, None) + if val is None: + continue + val_bytes = val.bytes if hasattr(val, 'bytes') else val + if val_bytes is None: + continue + label = uuid_label_cache.get((entity_name, label_field), {}).get(val_bytes) if label is not None: records[i][key_prefix + '_label'] = str(label) - # extraFields 字段 for ef in extra_fields: - ef_val = getattr(related_obj, ef, None) + ef_val = uuid_extra_cache.get((entity_name, ef), {}).get(val_bytes) if ef_val is not None: records[i][key_prefix + '_' + ef] = ef_val + else: + # ForeignKey 类型:直接获取关联实例 + related_obj = getattr(obj, field_name, None) + if related_obj is not None: + label = getattr(related_obj, label_field, None) + if label is not None: + records[i][key_prefix + '_label'] = str(label) + for ef in extra_fields: + ef_val = getattr(related_obj, ef, None) + if ef_val is not None: + records[i][key_prefix + '_' + ef] = ef_val except Exception: pass return records @@ -605,19 +778,13 @@ class FormService: except Exception: return {'records': [], 'total': 0} - # 强公司隔离:非管理员自动启用,仅返回当前公司/租户数据 + # 公司隔离:admin:super/admin:tenant 关闭,普通用户启用 + # 租户隔离:仅 admin:super 关闭,admin:tenant/普通用户启用 # 系统级表(Menu/Permission)豁免隔离 - if self.company_scope and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: - field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} - # 租户隔离:Tenant 表按 TenantUUID 过滤,只看自己所属租户 - if table_name == 'Tenant': - if not self.current_tenant_uuid: - import logging - logging.warning('[FormService] Tenant query but current_tenant_uuid is None, user=%s', self.user) - return {'records': [], 'total': 0, 'page': 1, 'PageSize': 20} - qs = qs.filter(TenantUUID=self.current_tenant_uuid) + field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} + if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: # 公司隔离:有 CompanyUUID 字段的表按当前公司过滤 - elif 'CompanyUUID' in field_names: + if self.company_scope and 'CompanyUUID' in field_names: if not self.current_company_uuid: # 无公司上下文时返回空结果 import logging @@ -634,22 +801,38 @@ class FormService: else: # UUIDField(包括 Company 表自身的主键) qs = qs.filter(CompanyUUID=self.current_company_uuid) - # 租户隔离:有 TenantUUID 字段但无 CompanyUUID 的表(Role/CompanyInfo 等)按租户过滤 - elif 'TenantUUID' in field_names: + # 租户隔离:admin:super 关闭,admin:tenant/普通用户启用 + # 覆盖三种表结构: + # 1. Tenant 表自身:按 TenantUUID 主键过滤 + # 2. 有 TenantUUID 字段的表(Role/CompanyInfo 等):直接按 TenantUUID 过滤 + # 3. 有 CompanyUUID ForeignKey 但无 TenantUUID 的表(Department/Workgroup): + # 通过 Company 关联跨表过滤到当前租户 + elif self.tenant_scope and (table_name == 'Tenant' or 'TenantUUID' in field_names or 'CompanyUUID' in field_names): if not self.current_tenant_uuid: import logging logging.warning('[FormService] tenant scope but current_tenant_uuid is None, user=%s, table=%s', self.user, table_name) return {'records': [], 'total': 0, 'page': 1, 'PageSize': 20} - # 判断 TenantUUID 字段类型:ForeignKey 用 _id 后缀 - tenant_field = None - for f in model._meta.get_fields(): - if hasattr(f, 'name') and f.name == 'TenantUUID': - tenant_field = f - break - if tenant_field and tenant_field.__class__.__name__ == 'ForeignKey': - qs = qs.filter(TenantUUID_id=self.current_tenant_uuid) + if table_name == 'Tenant' or 'TenantUUID' in field_names: + # 表自身有 TenantUUID 字段:直接过滤 + tenant_field = None + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'TenantUUID': + tenant_field = f + break + if tenant_field and tenant_field.__class__.__name__ == 'ForeignKey': + qs = qs.filter(TenantUUID_id=self.current_tenant_uuid) + else: + qs = qs.filter(TenantUUID=self.current_tenant_uuid) else: - qs = qs.filter(TenantUUID=self.current_tenant_uuid) + # 有 CompanyUUID ForeignKey 但无 TenantUUID:通过 Company 关联跨表过滤 + company_field = None + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'CompanyUUID': + company_field = f + break + if company_field and company_field.__class__.__name__ == 'ForeignKey': + qs = qs.filter(CompanyUUID__TenantUUID=self.current_tenant_uuid) + # UUIDField 类型的 CompanyUUID(如 Company 表自身)必有 TenantUUID 字段,已走上面分支 # 角色作用域过滤:针对 User/UserRole/Order 表按角色差异化可见范围 # admin 看全部,dept_manager 看本部门,workgroup_lead 看本工作组,member 只看自己 @@ -711,10 +894,21 @@ class FormService: total = qs.count() start = (page - 1) * page_size # 识别 entityPicker 字段,使用 select_related 预加载关联实体 + # 注意:select_related 只能用于 ForeignKey,UUIDField 类型的 entityPicker 不能预加载 ep_fields = self._get_entity_picker_fields(model) if ep_fields: - fk_names = [f['field_name'] for f in ep_fields - if f['field_name'] not in ('RoleUUID', 'PermUUID', 'PositionTemplateUUID', 'PositionUUID')] + excluded = {'RoleUUID', 'PermUUID', 'PositionTemplateUUID', 'PositionUUID'} + fk_names = [] + for ep_info in ep_fields: + fn = ep_info['field_name'] + if fn in excluded: + continue + # 只对 ForeignKey 字段使用 select_related,避免 UUIDField 触发 FieldError + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == fn: + if f.__class__.__name__ == 'ForeignKey': + fk_names.append(fn) + break if fk_names: qs = qs.select_related(*fk_names) page_qs = qs[start:start + page_size] @@ -765,10 +959,10 @@ class FormService: is_fk = field.__class__.__name__ == 'ForeignKey' attr_name = field.attname if is_fk else resolved create_data[attr_name] = val - # 强公司隔离:当启用 company_scope 且当前角色有公司上下文时, - # 自动注入当前公司 UUID(若模型有 CompanyUUID 字段且未显式提供) + # 自动填充当前公司 UUID:若模型有 CompanyUUID 字段且前端未显式提供, + # 默认使用当前角色对应的公司(适用于所有用户,包括管理员)。 # 系统级表(Menu/Permission/Tenant/Company 等)豁免,不自动注入公司 - if self.company_scope and self.current_company_uuid and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + if self.current_company_uuid and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} if 'CompanyUUID' in field_names: pk_field = model._meta.pk @@ -791,6 +985,22 @@ class FormService: create_data['CompanyUUID_id'] = self.current_company_uuid else: create_data['CompanyUUID'] = self.current_company_uuid + # 强租户隔离:当启用 tenant_scope 且当前角色有租户上下文时, + # 强制注入当前租户 UUID(覆盖用户传入值,防跨租户注入) + # 系统级表(Menu/Permission)豁免;admin:super(tenant_scope=False)不注入,允许跨租户 + if self.tenant_scope and self.current_tenant_uuid and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} + if 'TenantUUID' in field_names: + # 判断 TenantUUID 字段类型:ForeignKey 用 _id,UUIDField 用字段名 + tenant_field = None + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'TenantUUID': + tenant_field = f + break + if tenant_field and tenant_field.__class__.__name__ == 'ForeignKey': + create_data['TenantUUID_id'] = self.current_tenant_uuid + else: + create_data['TenantUUID'] = self.current_tenant_uuid pk_field_name = model._meta.pk.name if model._meta.pk else None if pk_field_name and pk_field_name not in create_data: pk_field = model._meta.pk @@ -838,13 +1048,38 @@ class FormService: pk_field_name = model._meta.pk.name pk_field = model._meta.pk pk_value = self._convert_value_for_field(pk_field, pk) + # 构造查询条件:附加租户/公司过滤防止跨域越权 + lookup = {pk_field_name: pk_value} + if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} + # 租户隔离:tenant_scope 启用时附加 TenantUUID 过滤(防跨租户改写) + if self.tenant_scope and self.current_tenant_uuid and 'TenantUUID' in field_names: + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'TenantUUID': + if f.__class__.__name__ == 'ForeignKey': + lookup['TenantUUID_id'] = self.current_tenant_uuid + else: + lookup['TenantUUID'] = self.current_tenant_uuid + break + # 公司隔离:company_scope 启用时附加 CompanyUUID 过滤(防跨公司改写) + elif self.company_scope and self.current_company_uuid and 'CompanyUUID' in field_names: + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'CompanyUUID': + if f.__class__.__name__ == 'ForeignKey': + lookup['CompanyUUID_id'] = self.current_company_uuid + else: + lookup['CompanyUUID'] = self.current_company_uuid + break try: - obj = model.objects.get(**{pk_field_name: pk_value}) + obj = model.objects.get(**lookup) except model.DoesNotExist: return None - # 强公司隔离:company_scope 启用时校验目标记录归属当前公司(系统级表豁免) - if self.company_scope and self.current_company_uuid and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES and not self._check_company_owner(obj): - return None + # 归属校验:company_scope 或 tenant_scope 任一启用时校验(系统级表豁免) + if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + if self.company_scope and self.current_company_uuid and not self._check_company_owner(obj): + return None + if self.tenant_scope and self.current_tenant_uuid and not self._check_tenant_owner(obj): + return None # 审计日志:修改前捕获旧数据快照 old_snapshot = self._get_record_snapshot(obj) if table_name in self.AUDIT_TABLES else None pascal_map = self._build_field_map(model) @@ -857,6 +1092,10 @@ class FormService: continue if resolved == model._meta.pk.name: continue + # 保护归属字段:tenant_scope 启用时禁止改写 TenantUUID/CompanyUUID(防数据迁移攻击) + # admin:super(tenant_scope=False)不受此限制,可做跨租户数据迁移 + if self.tenant_scope and resolved in ('TenantUUID', 'CompanyUUID') and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + continue field = pascal_map.get(resolved) or pascal_map.get(key) if field is None: continue @@ -897,13 +1136,38 @@ class FormService: pk_field_name = model._meta.pk.name pk_field = model._meta.pk pk_value = self._convert_value_for_field(pk_field, pk) + # 构造查询条件:附加租户/公司过滤防止跨域越权 + lookup = {pk_field_name: pk_value} + if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + field_names = {f.name for f in model._meta.get_fields() if hasattr(f, 'name')} + # 租户隔离:tenant_scope 启用时附加 TenantUUID 过滤(防跨租户删除) + if self.tenant_scope and self.current_tenant_uuid and 'TenantUUID' in field_names: + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'TenantUUID': + if f.__class__.__name__ == 'ForeignKey': + lookup['TenantUUID_id'] = self.current_tenant_uuid + else: + lookup['TenantUUID'] = self.current_tenant_uuid + break + # 公司隔离:company_scope 启用时附加 CompanyUUID 过滤(防跨公司删除) + elif self.company_scope and self.current_company_uuid and 'CompanyUUID' in field_names: + for f in model._meta.get_fields(): + if hasattr(f, 'name') and f.name == 'CompanyUUID': + if f.__class__.__name__ == 'ForeignKey': + lookup['CompanyUUID_id'] = self.current_company_uuid + else: + lookup['CompanyUUID'] = self.current_company_uuid + break try: - obj = model.objects.get(**{pk_field_name: pk_value}) + obj = model.objects.get(**lookup) except model.DoesNotExist: return False - # 强公司隔离:company_scope 启用时校验目标记录归属当前公司(系统级表豁免) - if self.company_scope and self.current_company_uuid and table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES and not self._check_company_owner(obj): - return False + # 归属校验:company_scope 或 tenant_scope 任一启用时校验(系统级表豁免) + if table_name not in self.COMPANY_SCOPE_EXEMPT_TABLES: + if self.company_scope and self.current_company_uuid and not self._check_company_owner(obj): + return False + if self.tenant_scope and self.current_tenant_uuid and not self._check_tenant_owner(obj): + return False # 审计日志:删除前捕获旧数据快照和目标 UUID old_snapshot = self._get_record_snapshot(obj) if table_name in self.AUDIT_TABLES else None _audit_target_uuid = obj.pk if table_name in self.AUDIT_TABLES else None @@ -947,7 +1211,7 @@ class FormService: def _check_company_owner(self, obj): """校验对象是否归属当前公司(用于 company_scope 下的修改/删除保护)。 - 若模型无 CompanyUUID 字段则放行(不隔离)。 + 若模型无 CompanyUUID 字段则放行(依赖 _check_tenant_owner 或外层 lookup 过滤)。 CompanyUUID 可能是外键(Department/Workgroup)或主键(Company 本身)。""" try: field_names = {f.name for f in obj._meta.get_fields() if hasattr(f, 'name')} @@ -962,7 +1226,20 @@ class FormService: obj_company = getattr(obj, 'CompanyUUID_id', None) or getattr(obj, 'CompanyUUID', None) return obj_company == self.current_company_uuid except Exception: - return True + return False # 异常时 fail-closed(拒绝),避免吞异常后越权 + + def _check_tenant_owner(self, obj): + """校验对象是否归属当前租户(用于 tenant_scope 下的修改/删除保护)。 + 若模型无 TenantUUID 字段则放行(依赖外层 lookup 过滤或 _check_company_owner)。 + TenantUUID 可能是外键或 UUIDField。""" + try: + field_names = {f.name for f in obj._meta.get_fields() if hasattr(f, 'name')} + if 'TenantUUID' not in field_names: + return True + obj_tenant = getattr(obj, 'TenantUUID_id', None) or getattr(obj, 'TenantUUID', None) + return obj_tenant == self.current_tenant_uuid + except Exception: + return False # 异常时 fail-closed(拒绝) def _get_record_snapshot(self, obj): """将模型实例转为可 JSON 序列化的字典(用于审计日志数据快照)""" diff --git a/gvsds/apps/gerp/migrations/0013_workgroup_role.py b/gvsds/apps/gerp/migrations/0013_workgroup_role.py new file mode 100644 index 00000000..90280357 --- /dev/null +++ b/gvsds/apps/gerp/migrations/0013_workgroup_role.py @@ -0,0 +1,35 @@ +"""Create WorkgroupRole table: 工作组与角色多对多关联 + +工作组只绑定角色,不直接绑定用户。用户通过持有该角色即成为工作组成员。 +手动创建此迁移以避免 makemigrations 检测到其他模型的预存字段名冲突。 +""" +import datetime +import django.db.models.deletion +import gvsds.apps.core.models +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('gerp', '0012_doc_editor_menu'), + ] + + operations = [ + migrations.CreateModel( + name='WorkgroupRole', + fields=[ + ('WorkgroupRoleUUID', gvsds.apps.core.models.UUIDField(db_column='WorkgroupRoleUUID', primary_key=True, serialize=False, verbose_name='关联UUID')), + ('RoleUUID', gvsds.apps.core.models.UUIDField(db_column='RoleUUID', verbose_name='角色UUID')), + ('RoleType', models.SmallIntegerField(db_column='RoleType', default=0, verbose_name='角色类型')), + ('CreateTime', models.DateTimeField(db_column='CreateTime', default=datetime.datetime.utcnow, verbose_name='创建时间')), + ('WorkgroupUUID', models.ForeignKey(db_column='WorkgroupUUID', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='gerp.workgroup', verbose_name='工作组UUID')), + ('CompanyUUID', models.ForeignKey(db_column='CompanyUUID', on_delete=django.db.models.deletion.CASCADE, related_name='+', to='gerp.company', verbose_name='公司UUID')), + ], + options={ + 'db_table': 'WorkgroupRole', + 'verbose_name': '工作组角色关联', + 'verbose_name_plural': '工作组角色关联', + }, + ), + ] diff --git a/gvsds/apps/gerp/migrations/__pycache__/0013_workgroup_role.cpython-312.pyc b/gvsds/apps/gerp/migrations/__pycache__/0013_workgroup_role.cpython-312.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4daaaa3593c4814e52374dc76d2b04772dd758bb GIT binary patch literal 2495 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 diff --git a/gvsds/apps/gerp/views/__pycache__/order_flow.cpython-312.pyc b/gvsds/apps/gerp/views/__pycache__/order_flow.cpython-312.pyc index 188ef245ff8fad99531a04d40c5dc1c39239278b..255a35bf2a3a504d5b8b1b2bbf8f4b4e299a0c9d 100644 GIT binary patch delta 1420 zcmah|&u^eB3NAyT`GszU9-Ru^dX^ zp?n!TUdcy&1<8<|2q8_-B+Wa^Y5wQ+WzJ!<$Y^!*w1J97TP4Dp?4BYG6zeP9Ox2EK z4Lxrx^Ft0*@>9Q_sHRPy-Sr2jOEN{e>h`EwDpk5Q8qF?G zxhBgk1&odg4u&wf30?Bk3}B%x2oWtVLcn zE&r!jt|^q=J-MGzYo7IJ(b>uml0~bK(diC~BeJTb)NZJzm*VtdPvxdwWlG5+4V5fg z#K!uX`jl0_!HxAzu2;2Mi2anKo5ED7Y(7%`@~63@fBIJY7tv+@C@NyL#mqE%Fw+8k zIAuNwbYi@cy$@cAk+9XCz>Oh3fRqLE-rYH4Khbi8bh9ZX-`WM4&5#kZ!|CBe*^`;{ zj-8q8JU^N<)DWu+-C#4Jyb!XEuscGCMZ$4BWcG$nVE@-8Xzj)qT_4n*hrxI0L+0zq z7rf5n9T3o*P@n1G=fuc>g$WFPM+0D&nrY|X#(;U-UYF@$ADcgla7k328~`a+vBJce36dW%$ax_10OJ6AEl@dU z-bu?Zz?F~12)rZNW4>zrEZ`J-9PeB5XY8l=Fz#W!iK!6m(Anz+%bOPlcMhk?5PP49 zDE3)fjV;6rC%3a-iFCiM7>1GoDDo6X$oSa!s46-V4nhWWkSxF{fDwQr0LK7Q?0WkF zJZOGsU%=rUF#7=Z01R6243V?!-I~qfSzuOMpvK7zQ&b)Iv8(EkatnBv?ud_qw~H;P l*Hl~a4TzzP+ySt=z&3t(x8^6bi|2FO$Rl=p?Kc53zX57W1a$xa delta 1582 zcmah}O>7%Q6rS02Vh4NeUH|MRcAK`c(`M6_fKn_%8WIOms@A4W?WQV<8ccSRc!M2x zys0S~CvZXG1`ItBr1k(3N)MpD7OD5N5&@)E77{`N38WslAS6!gv%7W-MeN~w|L=YC zX6((`%}c_LAUuWa@8>^H8^3-fthuGRe7RhiThd8+?pi^wR^}|`R$O??+Vs*uPq}~Zqz7?`Mmmmxj4_1Rw}Fsbq8ts<5PKAeb;WFo5++(xC1aBP9*Y$v zW6JTh<&%$UzU*W?>E8dNX2hw1xIHn-eh|GI!T;$MG~0DSXOGT3Yx}m`+WxK93(BH6 zGM{ngBoK+$i*(@SplVvADWxV$cBk%nrvtYY)Tp86AZebTXwQAcY_t(V=AV$bza zxpgzq_ETBlE2rW%JP=g){b8l_xn{kXd#%)`u@xdaR!pTf~X|h!Hu- zSpTWl|KZF+1Ue8Apa@X1q25!dtS+vSV%{XH;4A}N1$fm4m21v>!`3VCr*iNhks+A2 zMi2bv!buiSOmsgUyPU}3arSj$U55QOcW&BR-IbZnrN|jJGte7p^TG;vj@Y0k2xB(}4V?N&Jsk;eyEDtTQ1= diff --git a/gvsds/apps/gerp/views/__pycache__/role_permission.cpython-312.pyc b/gvsds/apps/gerp/views/__pycache__/role_permission.cpython-312.pyc index 14c41f9ccf3da7b011528df534cb4191e21bb158..4aa98ce771dd6897b5c700c70bdaf25c278d3b67 100644 GIT binary patch delta 1744 zcmZvcO>7%Q6o6;eYsb#+`fvT8IN2snlTAvKrlIOzS`t!~R*9j1v`r(7$*x=*J1*l* z>0v`@RiPfZqz}fqweT68{2i9k$R*@)UFA2YU2Tax*>5tvUH zotMab(vUb)tDnV_X@Tq~M(`3bLS|sy^RH><)}PaAa;OKed0~0-P6j(DZvBiNcW(_v3g@o9A>ti|{dP399B2i{ey8 z#1L^r7?I#;V5}NTaXNCrsuz}*%SNGLG1k+RJ4)1d*+RA;NJMN60p*(5g}Fvepa%uAf=>8TVKiI3UAa3OZ6ncr4Q#E7p6wqz^2xf;Ld-bf%T{p}Txzt#XDuuJZS zy}kmutUkxF`nj@F7aln?RO=LVYD7QS{diwRd^=o_e3ibn7)!;6ILPySy2PT z+Rj&R4P_p!jkzgI1u+GQ?uO5mWUp#~cEncgu(kJ*BnuaUN$K&thtCr}qj$|~ zXz*#UOB!pNR<^-!!9i*Jf9A>10Nv1>40$LV3hTZ;u3|ZOGd!lCmF{H|C>e)O!u=X5 zb%Ei>@gJ^N&3v(D-k>bklIN~ODm}O=uTz$FWsb0U%%P6 zqw4IC=k6dyj;M3gbC1wD3csYrGqbqUK8}a{wnmNn0lp_CF=Dq*NM|nt(QvWrII6J+)M?kkJEmb&|f;v zaJdsGnbs_~F&T4%xX6X<2*RzNdww;Xx?4XP&fosswYsrfHTSS<+yw9Q#%cIf-zc8C NPfmd}JS}4A{{ZtKY5xEK delta 1891 zcmZuxYiv_h96#swv3qy#?b_Xzw(Hui?IBD40S2Jm)c9a&U5cz zVk-&m3yCJ;S=0|pSYjgLFcBLg`a!>7j1LS04B^I)lJG$j2_`0*7|;Kf;z&G6fA@d> z?^n;;W6NXm_p;oK*!kkm$Mv(P`mg7ZjHp3Gyqe-1n)TW3JuB`wHfFGa*Ai%(Y1UpEOp(Rb4<;j_uf;wrK zghXrWoATIw$&JuEi2lZ}t6k#%z<4{5K2M`CwdF_`b0yLk`Kzuu~OGh-Du$M7PUPx?2o6lqHT1#v(4i8aTg-z9Ob@J3S+Y!?A{LRj$Yt z@hB#IMb0BNNS8%rO(@L7dZMB8-`7kv=!&FwQlBS6c_~0wOY5asY;YK zq=pwUQZ6;%}hnOm9BrU-w&{l1xhg_&P}gqcNgA9V3Vl-Vg} zQ=>99R`FGoir3tEpA(-$74JT@bdS;Sxk97k|25Raf=f*eyT`(L1NN(fEiygm`(`7kkR1Rn21Dmi#zFSLbuR-8PaE)u>i~6N?xWANeki3u z6JzISH*Sf!TIRQ5g zaQl1EcfAALpn!hpQ3l=l2FL)|Q%wQ|Wm&?oMoQ!3PwX@70T6f_Nf-S%k;LohwL~HgJ39l9 z(CxEt-9GinFBjjq_0hYx7CtDDcaOoPBfPey>_$f~M6 zeKHC>SRKoqHOr+!jtKTe)ruNh26$sF7d)W7{`&hX9-1j0namL_%f#*UM5=S`0q`*a za1h`*u<=Z@cGX|rA`4EQ2T7J*PJJ!GQdrLEvSA)?XT=3;pLQ-f3MEjO0w@Da1Iz=! z1c^N?dz%cn0)Wtv8O)(OoaEx~wvS+!Sl0wOYY;fGnu_IozA%-yoKOa7rC)Y!#z}go z>xB_ZVEeeAi8Vs(_^&YoG6rDx(cXW1W$dUAX7U%mqf}OBidsKeU>0o1$k6EfnKXWq LUdilnLdyRF!c3I+ diff --git a/gvsds/apps/gerp/views/__pycache__/tenant_review.cpython-312.pyc b/gvsds/apps/gerp/views/__pycache__/tenant_review.cpython-312.pyc index 2178065dc320aa64fd4abc8d3067d7b491f04b35..8693a563ff6729e3fefb86bb23a4c7bbd2b8049d 100644 GIT binary patch delta 2610 zcmah~eQZ-z6u-Bh>(;S#?b`0UZ*0>yWCK5Rkwm=sAgy!b&P?Y6%#G84sryyw2Ho5aMI^!M&P_ndRj zJ@0paebf8xk~!aGXJ;bztms+VdAxA%95?=X?@iktA3$sKbZ7|*XAYonmSlNX4^9ZJ zwR5YLS+i$Z9o#yHS#xGt^SL#bS#7hd&R(6EM|1Jtt`OFea)b@wcdeA(p(``DAWj*JvusywgMMuXmOA;?Q=TP0f3qj`!87B>Uer=2pF5^y{B`S7d>5@MMT!Qa z3JaRrbjSAD^`u@Jo+^WvJsnDPtOFuetq;j9aH@JZpib1$2lYaQs8h{EdLk~#3W0AX zgR{qTeW7>Jx|YWJrurqmb@Yt>4q*IVVpPT&B#tx}x%H*VnEOlZDTObfX${2`x;)Zbn z1Jw`?D#3}b!JJRZsWGz)3slama^DbRRt>TgX(v{CJku{^^U&^?tZa5@CDeC!M|xMW zjcdSKHBynK*VWy{+}=hchrI}d5Zg2eY#>8L1L%^W?Q)@*D5y>toYxE$MwjMq;|Ctj zenYni`a+5rh?pN>bJqL@A_WzxwWEu%2v!}iY9up7LobMvKAG$4=Z7r^2OEJl6QC@~ z01yGH0cshj88H%;NML=hY6^CDOVKb{0BiFB76SY`ziR1_W9>206;y~5mc0yE6}Q6J z%0SgUArm)jtztzsvNtf=qxGVK-eN1oe!9%|ix)c7B$~-0_9piLEC#3pFaX>OPyuis zz)VN$VG7Vlqj|+9zEICvw;VUq%Xvl1Q|Gs)PK-|R`NNarJKsq@eIz+}AUS%beB!{4 zbFdgV7rF>_89Nq+f%2u;^+c4fJxp zVA2>w%}ybskh#5$NeNxx4B;~Rg0rSS4qZz(Ks!G%GaI|r?ht2MK$C_5*NFg$MI<%D z&#)=jCB@;ZY05CMb^rMJoynm+T8D4YG%a)TjpO4d-t=e*>ndepg5u-V^lPWk%rq={ z>U}L*ESyEx84kkLAvXieP#Bn@TD>tzZi*?sO&v^a;HHyWTH`9}SIrwFrVt8I4RKi_ zWF;(BL5L}?grcz?vI-^!Ik-_$tw87ibKBSvlE@lZu=qBGq;91n7FA7=*oF-fQ7zK6 ziQfq^V;1SNrg{~RcZ5}&BGEmr+~zxZB4}J7%6g_HHo#nlWAJ;Cz8at#o1JN4A+e28 zT3R-+HF+4*6*fCnOLx+kyP@&2qjJ#lo6cm3;-9SdU9!(R^1wy=ZG+xRIrblm=MH<1 zRKFL#SbWcS#SfnD9kNWCP_BE}mYBaf@klVSdVOME=z=?ZF-J;dNPkYwqkok(_WwOu zigF8nN0_Dj(VF*zwfKUyI8m}D(H8unEpnkP^1Zd|H$ALgmv3hk9W3(-=$yxg&}X%R zPeh*!1>QRS=Zi7JFVI|HmHvxL3@C~Zn*rCepA5nV8o73s zMiv^|u_gh_xJaHU*r~t20pc0~(sD^aaYz#Yu%0XjXa-mT;0I^{;Ns<=+e(DuRyG$6 z8hf~zlLh!eR@0A5D*I;?^#3VI+8|D2B8)%@NhJf->G!RA$hSJs(pulTvL)d6E%i0D z`n*~tkAmA{0BNR@08CjKk0SCo8;YhG!VwA9+5tGrvWe6wg_HohxNnEnJr83C%@Kr^T*^Zb zRSU2VAOgS}$o~krcJuGev;SM!uV*aoiiP75>0TnUFi=%_2i+|!HUDlhV`0)kCxoK% bGTe?6?%GKNgGnzg#6FyG)%<~Az&Y{{K~3Sw delta 2149 zcmah}ZA?>F7{0eHZE0ztE%pn9f(W@dYv%k2hJrwgI7?-f;bfcGaW5YZT0Hj_CSwCL zKDU{nXCEe-=zchLPF;q}*wF09{Nv1$63vD;{?LVsxIZqFC5zjSo%ddaYL@HD3C&apzedWakeVA4AJ}50{G;ok76}!%H#KXOVy`Bxq@yns$#WgRf>gd zeX6~Be86nOrRG#qHw6dn*D?C|ZVFeb3j6P#|Mc$oFsXk3(&$#cg6(XN-oG?9H~O)3 z8B1da=T9A;ANxugKPVkLEe#!*KR z8$CA)eA7nXjLk7)%$T4xDJirQ>ryusImvNiT0IwyM1>vXwc|X3GGbbo>u%?I9=WnG zm+E8kNdUnHR6u}Hqvf#9As}klh!DAV72L~E5x$n%Nf+W0L!tAjJvvdta~&nN|S%V<^IUQ?^zP*e8ljw^jYw5b_XUX!%`wjjO8= z7ZJGn_D&A1gs2OmDQKxaTnl9p5DiJWGVLnmCPlEtf}qt1x{ve#UVt?K>j;SY7-I2Q zhPi(|xYhz}0C=*HnA*iFE~ zH`36_duOJ89r%#^%}x!?eLpaFaY_jh0VYq7?^&Y5`mzM z9JA-?b4lhI8s+07_GY>O|6}*+WO2uH@C8SnpUF%jc!|D3vJ0&SSRx(zCZ_pg93P4a zfj0;R^01+G_)e~^q@u1ayuBgdNAT?vHF2VU1Pwx~z}Civ=4h-3y(|a$NH@30KK{&E zu%VRrpXMDD&}&zCujuMRv2IT0yeq~FosjRt1D>P!qN^(NR=VSaC9!JTv@>`;y*8n* z{cFL6{qEYs`TFLnP1I$N-$nhf$>q1JuPmbhT`TMLb5!4N7Vhdn(Ib>IVNgGw+c zFc56gKv+^jv@#KOFcA|%)`+^Y2DXI(7Bz`roDs|w0xnQJz>5G406PI-h81DBurD_+ zdl#`8jnZDwsM1Mj9(o1$=B}(<66ODMhIYd~_(sW+m1TyC32E8o^4UNb+5@o27m9!> z*L#hu810f$Q8R>CfKpuQ$BDd0$BhDa83Slhpap>8K)vo z`5LQmUw)8w;j8(1uY!g}ZAYxNl?>84*xdvGbf63Xxdrk^u^LR7FuBT;(tfBf?ToSU z4z3J!kvtF%KaR^+Ja7G7OXYYI+1>=@`a^4=Eemcuz9K8nPKW4(bIk$;i-omrIzT5J L-ajc=$bA0`7H<(0 diff --git a/gvsds/apps/gerp/views/__pycache__/viewsets.cpython-312.pyc b/gvsds/apps/gerp/views/__pycache__/viewsets.cpython-312.pyc index 1892f991aa7fe1c16df6ebd604efbbc3f96f44e8..ecda980a2aa1b5e1cabd35fd8b597933ad6a8c9b 100644 GIT binary patch delta 19479 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 delta 14320 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 diff --git a/gvsds/apps/gerp/views/form_api.py b/gvsds/apps/gerp/views/form_api.py index 59be992e..89b71351 100644 --- a/gvsds/apps/gerp/views/form_api.py +++ b/gvsds/apps/gerp/views/form_api.py @@ -1,6 +1,7 @@ import logging import uuid as _uuid from rest_framework.response import Response +from gvsdsdk.model_utils import UUIDObj from . import ELTAuthenticatedAPIView from gvsds.apps.gerp.form_service import FormService @@ -28,11 +29,20 @@ def _get_current_context(request): def _is_admin(request): - """判断用户是否为管理员(委托给 gvsdsdk.auth.admin_service.IsAdmin)""" + """判断用户是否为管理员(admin:super 或 admin:tenant,委托给 IsAdmin)""" from gvsdsdk.auth.admin_service import IsAdmin return IsAdmin(request) +def _is_super_admin(request): + """判断用户是否为跨租户管理员(仅 admin:super,委托给 IsSuperAdmin) + + 用于租户隔离判定:仅 admin:super 关闭租户隔离。 + """ + from gvsdsdk.auth.admin_service import IsSuperAdmin + return IsSuperAdmin(request) + + def _make_service(request, user): """构造 FormService,自动传入当前公司 UUID、角色 UUID 和租户 UUID。 上下文来自 CurrentRoleMiddleware 注入的 request 属性。 @@ -48,9 +58,9 @@ def _make_service(request, user): ip_address = x_forwarded_for.split(',')[0].strip() else: ip_address = request.META.get('REMOTE_ADDR', '') - # 计算角色作用域(管理员特权模式下不应用作用域过滤) + # 计算角色作用域(管理员不应用作用域过滤) role_scope = None - if not (admin_verify and _is_admin(request)): + if not _is_admin(request): from gvsds.apps.gerp.role_scope import GetUserScope try: role_scope = GetUserScope(request) @@ -80,23 +90,25 @@ _ACTION_PERM_SUFFIX = { def _check_rbac_permission(table_name, action, request): """RBAC 权限码校验:构造 {Table}:{suffix} 权限码,检查当前角色是否拥有。 - 向后兼容:若该权限码在 Permission 表中不存在,返回 None(回退到原有逻辑)。 - 若权限码存在但当前角色未分配,返回 403 错误响应。 + 返回 (error_response, perm_defined): + - perm_defined=False: 权限码未定义,调用方应回退到原有逻辑 + - perm_defined=True, error_response=None: 权限码存在且当前角色已分配 → 放行 + - perm_defined=True, error_response=Response: 权限码存在但当前角色未分配 → 拒绝 """ suffix = _ACTION_PERM_SUFFIX.get(action) if not suffix: - return None + return None, False perm_code = f'{table_name}:{suffix}' from gvsds.apps.gerp.models import Permission, RolePermission # 检查权限码是否在系统中定义 perm_qs = Permission.objects.filter(PermCode=perm_code, PermStatus=1) if not perm_qs.exists(): - return None # 权限码未定义,回退到原有逻辑 + return None, False # 权限码未定义,回退到原有逻辑 perm_uuids = list(perm_qs.values_list('PermUUID', flat=True)) # 检查当前角色是否拥有该权限 current_role_uuid, current_company_uuid, _ = _get_current_context(request) if not current_role_uuid: - return Response({'code': 403, 'msg': f'无权操作:缺少权限 {perm_code}(未指定当前角色)'}, status=403) + return Response({'code': 403, 'msg': f'无权操作:缺少权限 {perm_code}(未指定当前角色)'}, status=403), True rp_qs = RolePermission.objects.filter( RoleUUID=current_role_uuid, PermUUID__in=perm_uuids, @@ -104,8 +116,8 @@ def _check_rbac_permission(table_name, action, request): if current_company_uuid: rp_qs = rp_qs.filter(CompanyUUID=current_company_uuid) if not rp_qs.exists(): - return Response({'code': 403, 'msg': f'无权操作:当前角色未分配权限 {perm_code}'}, status=403) - return None + return Response({'code': 403, 'msg': f'无权操作:当前角色未分配权限 {perm_code}'}, status=403), True + return None, True def _check_table_access(table_name, action, request): @@ -113,16 +125,23 @@ def _check_table_access(table_name, action, request): 校验顺序: 1. 管理员直接放行 - 2. RBAC 权限码校验(若权限码已定义则强制校验,未定义则跳过) - 3. ADMIN_ONLY_TABLES 黑名单 - 4. READONLY_TABLES_FOR_NORMAL 只读约束 + 2. RBAC 权限码校验: + - 权限码已定义 → RBAC 校验优先(通过则放行,未通过则拒绝,跳过黑名单/只读约束) + - 权限码未定义 → 回退到黑名单/只读约束 + 3. ADMIN_ONLY_TABLES 黑名单(仅权限码未定义时生效) + 4. READONLY_TABLES_FOR_NORMAL 只读约束(仅权限码未定义时生效) + + 设计意图:当管理员为某表配置了 RBAC 权限码并分配给公司负责人等角色时, + 该角色即可在权限范围内(受公司隔离限制)操作该表,不再受 READONLY 硬编码约束。 """ if _is_admin(request): return None - # RBAC 权限码校验(向后兼容:权限码未定义时返回 None 不阻断) - rbac_error = _check_rbac_permission(table_name, action, request) - if rbac_error: + # RBAC 权限码校验 + rbac_error, perm_defined = _check_rbac_permission(table_name, action, request) + if perm_defined: + # RBAC 驱动模式:权限码已定义,RBAC 校验结果决定是否放行 return rbac_error + # 回退模式:权限码未定义,使用原有黑名单/只读约束 if table_name in FormService.ADMIN_ONLY_TABLES: return Response({'code': 403, 'msg': f'无权访问表 {table_name},仅管理员可操作'}, status=403) if table_name in FormService.READONLY_TABLES_FOR_NORMAL: @@ -331,7 +350,7 @@ class FormAPIView(ELTAuthenticatedAPIView): class CheckAdminView(ELTAuthenticatedAPIView): - """检查当前用户是否具有管理员特权(超级用户或当前角色拥有 admin:super 权限) + """检查当前用户是否具有管理员特权(超级用户或当前角色拥有 admin:super 或 admin:tenant 权限) 额外返回 role_scope 信息,用于前端按角色作用域差异化显示: - admin: 管理员(看全公司) @@ -352,6 +371,7 @@ class CheckAdminView(ELTAuthenticatedAPIView): user = request.user if hasattr(request, 'user') and request.user.is_authenticated else None is_superuser = bool(user and getattr(user, 'IsSuperuser', False)) is_admin = _is_admin(request) + is_super_admin = _is_super_admin(request) current_role_uuid, current_company_uuid, current_tenant_uuid = _get_current_context(request) # 获取角色名 role_name = '' @@ -363,9 +383,11 @@ class CheckAdminView(ELTAuthenticatedAPIView): role_name = role.RoleName or '' except Exception: pass - # 判断隔离状态(与 _make_service 一致) - admin_verify = request.query_params.get('admin_verify') == '1' if hasattr(request, 'query_params') else False - company_scope = not (admin_verify and is_admin) + # 判断隔离状态(与 FormService.__init__ 一致) + # company_scope: admin:super/admin:tenant 关闭,普通用户启用 + # tenant_scope: 仅 admin:super 关闭,admin:tenant/普通用户启用 + company_scope = not is_admin + tenant_scope = not is_super_admin # 计算角色作用域(前端差异化显示依据) scope_type = 'member' @@ -378,9 +400,9 @@ class CheckAdminView(ELTAuthenticatedAPIView): scope_type = scope.Type scope_label = self.SCOPE_TYPE_LABEL.get(scope_type, '普通组员') if scope.DeptUUID: - dept_uuid_str = str(_uuid.UUID(bytes=scope.DeptUUID)) + dept_uuid_str = UUIDObj(scope.DeptUUID).str if scope.WorkgroupUUID: - workgroup_uuid_str = str(_uuid.UUID(bytes=scope.WorkgroupUUID)) + workgroup_uuid_str = UUIDObj(scope.WorkgroupUUID).str except Exception: pass @@ -389,12 +411,14 @@ class CheckAdminView(ELTAuthenticatedAPIView): 'data': { 'is_admin': is_admin, 'is_superuser': is_superuser, + 'is_super_admin': is_super_admin, 'current_role_name': role_name, - 'current_role_uuid': str(_uuid.UUID(bytes=current_role_uuid)) if current_role_uuid else None, - 'current_company_uuid': str(_uuid.UUID(bytes=current_company_uuid)) if current_company_uuid else None, - 'current_tenant_uuid': str(_uuid.UUID(bytes=current_tenant_uuid)) if current_tenant_uuid else None, + 'current_role_uuid': UUIDObj(current_role_uuid).str if current_role_uuid else None, + 'current_company_uuid': UUIDObj(current_company_uuid).str if current_company_uuid else None, + 'current_tenant_uuid': UUIDObj(current_tenant_uuid).str if current_tenant_uuid else None, 'has_current_company': current_company_uuid is not None, 'company_scope': company_scope, + 'tenant_scope': tenant_scope, # 角色作用域(前端差异化显示依据) 'role_scope': { 'Type': scope_type, diff --git a/gvsds/apps/gerp/views/menu.py b/gvsds/apps/gerp/views/menu.py index a39a1d9b..3d5444cc 100644 --- a/gvsds/apps/gerp/views/menu.py +++ b/gvsds/apps/gerp/views/menu.py @@ -4,7 +4,7 @@ 差异化显示策略(按优先级从高到低): 1. 公司过滤:公有菜单(CompanyUUID 为空)+ 当前公司私有菜单(对所有用户生效,包括管理员) 2. 祖先链校验:任何祖先不属于当前公司则整个分支移除(对所有用户生效) - 3. 管理员特权:IsSuperuser 或拥有 admin:super 权限的用户跳过权限过滤 + 3. 管理员特权:当前角色拥有 admin:super 或 admin:tenant 权限的用户跳过权限过滤 4. 权限过滤(仅非管理员):按 PermissionAttr 权限码校验,父节点无权限则整个子树隐藏 管理 API(仅管理员可访问): @@ -59,15 +59,18 @@ class MenuListCompatView(ELTAuthenticatedAPIView): role_uuid_bytes = None # 获取当前公司上下文(通过 UserRole.CompanyUUID) + # UserRole.CompanyUUID 是 UUIDField(返回 UUIDObj),不能 select_related current_company = None if role_uuid_bytes: try: user_role = UserRole.objects.filter( UserUUID=user.UserUUID, RoleUUID=role_uuid_bytes, - ).select_related('CompanyUUID').first() + ).first() if user_role and user_role.CompanyUUID: - current_company = user_role.CompanyUUID + # 转为 bytes,用于后续 Menu.CompanyUUID(ForeignKey)过滤 + cc = user_role.CompanyUUID + current_company = cc.bytes if hasattr(cc, 'bytes') else cc except Exception: logger.exception('MenuListCompatView 解析当前公司失败') @@ -114,7 +117,8 @@ class MenuListCompatView(ELTAuthenticatedAPIView): # 祖先链校验:任何祖先不属于当前公司 → 整个分支移除 # 对所有用户生效(包括管理员),防止公有子节点挂在私有父节点下导致结构泄漏 - current_company_uuid_str = str(current_company.CompanyUUID) if current_company else None + # current_company 已是 bytes(来自 UserRole.CompanyUUID),转为 UUID 字符串用于匹配 + current_company_uuid_str = str(_uuid.UUID(bytes=current_company)) if current_company else None invalid_uuids = set() for uuid_str in list(menu_map.keys()): if uuid_str in invalid_uuids: diff --git a/gvsds/apps/gerp/views/order_flow.py b/gvsds/apps/gerp/views/order_flow.py index 48dc35e9..4203d8ab 100644 --- a/gvsds/apps/gerp/views/order_flow.py +++ b/gvsds/apps/gerp/views/order_flow.py @@ -121,9 +121,9 @@ class OrderFlowView(ELTAuthenticatedAPIView): # 解析上下文 _, current_company_uuid, current_tenant_uuid = _get_current_context(request) - admin_verify = request.query_params.get('admin_verify') == '1' - # 管理员特权模式下不传 company_uuid(让 OrderManager 跨公司查询) - effective_company_uuid = None if (admin_verify and _is_admin(request)) else current_company_uuid + # 管理员(admin:super 或 admin:tenant)自动关闭公司隔离,跨公司查询 + # 注意:OrderManager 按 tenant_uuid 必需过滤,两种管理员都只看本租户订单 + effective_company_uuid = None if _is_admin(request) else current_company_uuid # 先查询订单(用于审计快照) order_uuid_bytes = _to_uuid_bytes(order_uuid) diff --git a/gvsds/apps/gerp/views/role_permission.py b/gvsds/apps/gerp/views/role_permission.py index e0ab468c..0a1f8677 100644 --- a/gvsds/apps/gerp/views/role_permission.py +++ b/gvsds/apps/gerp/views/role_permission.py @@ -12,7 +12,7 @@ from rest_framework.response import Response from . import ELTAuthenticatedAPIView from gvsds.apps.gerp.models import Role, Permission, RolePermission, Company -from gvsds.apps.gerp.views.form_api import _is_admin, _get_current_context +from gvsds.apps.gerp.views.form_api import _is_admin, _is_super_admin, _get_current_context logger = logging.getLogger(__name__) @@ -62,7 +62,7 @@ class RolePermissionsTreeView(ELTAuthenticatedAPIView): if not role_uuid: return Response({'code': 1, 'msg': 'RoleUUID 格式无效'}, status=400) - # 权限校验:仅管理员或当前角色拥有 admin:super 权限可分配 + # 权限校验:仅管理员(admin:super 或 admin:tenant)可分配 if not _is_admin(request): return Response({'code': 403, 'msg': '无权操作:仅管理员可分配角色权限'}, status=403) @@ -75,9 +75,9 @@ class RolePermissionsTreeView(ELTAuthenticatedAPIView): if not company_uuid: return Response({'code': 1, 'msg': '缺少 CompanyUUID 参数(无法确定权限分配的公司维度)'}, status=400) - # 查询权限(按租户过滤;管理员特权模式下可看全部) + # 查询权限(按租户过滤;仅跨租户管理员 admin:super 看全部,admin:tenant 保留租户隔离) perm_qs = Permission.objects.filter(PermStatus=1) - if tenant_uuid_bytes and not (request.query_params.get('admin_verify') == '1' and _is_admin(request)): + if tenant_uuid_bytes and not _is_super_admin(request): perm_qs = perm_qs.filter(TenantUUID=tenant_uuid_bytes) perm_qs = perm_qs.order_by('PermCategory', 'PermName') diff --git a/gvsds/apps/gerp/views/tenant_review.py b/gvsds/apps/gerp/views/tenant_review.py index bf35f9df..6e57621a 100644 --- a/gvsds/apps/gerp/views/tenant_review.py +++ b/gvsds/apps/gerp/views/tenant_review.py @@ -19,21 +19,15 @@ REVIEW_STATUS_PENDING = 0 REVIEW_STATUS_APPROVED = 1 REVIEW_STATUS_REJECTED = 2 -# 允许访问审核工作台的角色名集合 -REVIEW_ALLOWED_ROLE_NAMES = {'GERP 测试员'} - def _is_reviewer(request): - """判断用户是否有审核权限:管理员(IsAdmin)或当前角色名在允许集合中""" - from gvsdsdk.auth.admin_service import IsAdmin - # 管理员(超级用户/员工/admin:super 权限)直接放行 - if IsAdmin(request): - return True - current_role = getattr(request, 'current_role', None) - if current_role and hasattr(current_role, 'RoleName'): - if current_role.RoleName in REVIEW_ALLOWED_ROLE_NAMES: - return True - return False + """判断用户是否有审核权限:仅依据当前角色的 admin:super 权限(跨租户管理员) + + 租户审核是跨租户的系统级操作,仅 admin:super(如 GERP 测试员)可审核。 + admin:tenant(租户内管理员)无审核权限。 + """ + from gvsdsdk.auth.admin_service import IsSuperAdmin + return IsSuperAdmin(request) class TenantApplyView(ELTAuthenticatedAPIView): @@ -102,7 +96,7 @@ class TenantPendingListView(ELTAuthenticatedAPIView): class TenantApproveView(ELTAuthenticatedAPIView): - """批准租户入驻——仅激活租户(ReviewStatus=1),不自动创建公司/角色""" + """批准租户入驻——激活租户并自动创建"集团超级管理员"角色""" permission_classes = [IsAuthenticated] @@ -123,7 +117,16 @@ class TenantApproveView(ELTAuthenticatedAPIView): tenant.ReviewerUUID = getattr(request.user, 'UserUUID', None) tenant.ReviewTime = datetime.datetime.utcnow() tenant.save(update_fields=['ReviewStatus', 'ReviewerUUID', 'ReviewTime']) - return Response({'code': 0, 'msg': '审核通过,租户已激活'}) + # 审核通过后自动创建租户管理员角色 + role_name = '管理员角色' + try: + from ..form_service import create_tenant_admin_role + role = create_tenant_admin_role(tenant) + if role: + role_name = getattr(role, 'RoleName', None) or role_name + except Exception: + logger.exception('审核通过后创建租户管理员角色失败 tenant=%s', tenant.TenantUUID) + return Response({'code': 0, 'msg': f'审核通过,租户已激活,已创建{role_name}'}) class TenantRejectView(ELTAuthenticatedAPIView): diff --git a/gvsds/apps/gerp/views/viewsets.py b/gvsds/apps/gerp/views/viewsets.py index 4f7a630e..f5bbca42 100644 --- a/gvsds/apps/gerp/views/viewsets.py +++ b/gvsds/apps/gerp/views/viewsets.py @@ -8,7 +8,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from gvsdsdk.auth.permissions import IsAdminUser, CompanyScopedMixin, TenantScopedMixin +from gvsdsdk.auth.permissions import IsAdminUser, CompanyScopedMixin, TenantScopedMixin, UserScopedMixin from gvsdsdk.model_utils import UUIDObj from . import ELTAuthenticatedAPIView from ..models import ( @@ -37,7 +37,7 @@ from ..serializers import ( ) -class TenantViewSet(viewsets.ModelViewSet): +class TenantViewSet(TenantScopedMixin, viewsets.ModelViewSet): queryset = Tenant.objects.all() serializer_class = TenantSerializer permission_classes = [IsAuthenticated] @@ -50,7 +50,8 @@ class TenantViewSet(viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Tenant.objects.all() + # 使用 get_queryset() 自动应用租户隔离(管理员看全部,普通用户只看当前租户) + qs = self.get_queryset() if keyword: qs = qs.filter(TenantName__icontains=keyword) serializer = self.get_serializer(qs[:20], many=True) @@ -76,7 +77,8 @@ class CompanyViewSet(TenantScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Company.objects.all() + # 使用 get_queryset() 自动应用租户隔离(管理员看全部,普通用户只看当前租户) + qs = self.get_queryset() if keyword: from django.db.models import Q qs = qs.filter( @@ -101,7 +103,8 @@ class DepartmentViewSet(CompanyScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Department.objects.all() + # 使用 get_queryset() 自动应用公司隔离(管理员看全部,普通用户只看当前公司) + qs = self.get_queryset() if keyword: qs = qs.filter(DeptName__icontains=keyword) serializer = self.get_serializer(qs[:20], many=True) @@ -119,6 +122,18 @@ class DepartmentViewSet(CompanyScopedMixin, viewsets.ModelViewSet): user_bytes = UUIDObj(user_uuid).bytes if isinstance(user_uuid, str) else user_uuid except (ValueError, AttributeError): return Response({'code': 1, 'msg': '无效的UUID'}, status=400) + # 归属校验:非 admin:super 校验部门归属当前公司(防跨公司设置负责人) + from gvsdsdk.auth.admin_service import IsSuperAdmin + if not IsSuperAdmin(request): + current_company = getattr(request, 'current_company_uuid', None) + if hasattr(current_company, 'pk'): + current_company = current_company.pk + elif hasattr(current_company, 'bytes'): + current_company = current_company.bytes + if current_company: + dept = Department.objects.filter(DeptUUID=dept_bytes).first() + if dept and getattr(dept, 'CompanyUUID_id', None) != current_company: + return Response({'code': 403, 'msg': '无权操作:目标部门不属于当前公司'}, status=403) smtm, created = DepartmentSMTM.objects.update_or_create( DepartmentUUID=dept_bytes, UserUUID=user_bytes, @@ -140,7 +155,7 @@ class RoleViewSet(TenantScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Role.objects.all() + qs = self.get_queryset() if keyword: qs = qs.filter(RoleName__icontains=keyword) serializer = self.get_serializer(qs[:20], many=True) @@ -154,7 +169,7 @@ class WorkgroupViewSet(CompanyScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Workgroup.objects.all() + qs = self.get_queryset() if keyword: qs = qs.filter(WorkgroupName__icontains=keyword) data = [] @@ -182,6 +197,19 @@ class UserDeptViewSet(viewsets.ModelViewSet): ser = UserDeptSerializer_Create(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data + # 归属校验:非 admin:super 校验 DeptUUID 归属当前公司(防跨公司/跨租户操作) + from gvsdsdk.auth.admin_service import IsSuperAdmin + if not IsSuperAdmin(request): + current_company = getattr(request, 'current_company_uuid', None) + if hasattr(current_company, 'pk'): + current_company = current_company.pk + elif hasattr(current_company, 'bytes'): + current_company = current_company.bytes + if current_company: + from gvsds.apps.gerp.models import Department + dept = Department.objects.filter(DeptUUID=data['DeptUUID']).first() + if dept and getattr(dept, 'CompanyUUID_id', None) != current_company: + return Response({'code': 403, 'msg': '无权操作:目标部门不属于当前公司'}, status=403) existing = UserDept.objects.filter( UserUUID=data['UserUUID'], DeptUUID=data['DeptUUID'], @@ -203,6 +231,19 @@ class UserDeptViewSet(viewsets.ModelViewSet): ser = UserDeptRemoveSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data + # 归属校验:非 admin:super 校验 DeptUUID 归属当前公司(防跨公司/跨租户操作) + from gvsdsdk.auth.admin_service import IsSuperAdmin + if not IsSuperAdmin(request): + current_company = getattr(request, 'current_company_uuid', None) + if hasattr(current_company, 'pk'): + current_company = current_company.pk + elif hasattr(current_company, 'bytes'): + current_company = current_company.bytes + if current_company and data.get('DeptUUID'): + from gvsds.apps.gerp.models import Department + dept = Department.objects.filter(DeptUUID=data['DeptUUID']).first() + if dept and getattr(dept, 'CompanyUUID_id', None) != current_company: + return Response({'code': 403, 'msg': '无权操作:目标部门不属于当前公司'}, status=403) deleted, _ = UserDept.objects.filter( UserUUID=data['UserUUID'], DeptUUID=data['DeptUUID'], @@ -222,7 +263,34 @@ class RolePermissionViewSet(CompanyScopedMixin, viewsets.ModelViewSet): ser = RolePermissionAssignSerializer(data=request.data) ser.is_valid(raise_exception=True) data = ser.validated_data + # CompanyUUID 默认用当前公司上下文(序列化器未要求该字段) company_uuid = data.get('CompanyUUID') + if not company_uuid: + current_company = getattr(request, 'current_company_uuid', None) + if current_company is not None: + if hasattr(current_company, 'pk'): + company_uuid = current_company.pk + elif hasattr(current_company, 'bytes'): + company_uuid = current_company.bytes + # 归属校验:非 admin:super 校验 RoleUUID/PermUUID 归属当前租户(防跨租户注入权限) + from gvsdsdk.auth.admin_service import IsSuperAdmin + if not IsSuperAdmin(request): + current_tenant = getattr(request, 'current_tenant_uuid', None) + if hasattr(current_tenant, 'bytes'): + current_tenant = current_tenant.bytes + elif isinstance(current_tenant, str): + try: + current_tenant = uuid.UUID(current_tenant).bytes + except ValueError: + current_tenant = None + if current_tenant: + from gvsds.apps.gerp.models import Role, Permission + role = Role.objects.filter(RoleUUID=data['RoleUUID']).first() + if role and getattr(role, 'TenantUUID_id', None) != current_tenant and getattr(role, 'TenantUUID', None) != current_tenant: + return Response({'code': 403, 'msg': '无权操作:目标角色不属于当前租户'}, status=403) + perm = Permission.objects.filter(PermUUID=data['PermUUID']).first() + if perm and getattr(perm, 'TenantUUID_id', None) != current_tenant and getattr(perm, 'TenantUUID', None) != current_tenant: + return Response({'code': 403, 'msg': '无权操作:目标权限不属于当前租户'}, status=403) existing = RolePermission.objects.filter( RoleUUID=data['RoleUUID'], PermUUID=data['PermUUID'], @@ -265,7 +333,7 @@ class PermissionViewSet(TenantScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Permission.objects.all() + qs = self.get_queryset() if keyword: from django.db.models import Q qs = qs.filter(Q(PermName__icontains=keyword) | Q(PermCode__icontains=keyword)) @@ -279,6 +347,21 @@ class PermissionViewSet(TenantScopedMixin, viewsets.ModelViewSet): perm_code_raw = serializer.validated_data['PermCode'].strip() tenant_uuid = serializer.validated_data['TenantUUID'] + # 租户隔离:非 admin:super 强制使用当前租户 UUID(防跨租户创建权限) + # admin:super 可跨租户创建;admin:tenant 和普通用户只能创建本租户权限 + from gvsdsdk.auth.admin_service import IsSuperAdmin + if not IsSuperAdmin(request): + current_tenant = getattr(request, 'current_tenant_uuid', None) + if current_tenant is not None: + if hasattr(current_tenant, 'bytes'): + tenant_uuid = current_tenant.bytes + elif isinstance(current_tenant, str): + try: + tenant_uuid = uuid.UUID(current_tenant).bytes + except ValueError: + pass + elif isinstance(current_tenant, bytes): + tenant_uuid = current_tenant perm_name = serializer.validated_data.get('PermName', '') perm_category = serializer.validated_data.get('PermCategory', '') perm_status = serializer.validated_data.get('PermStatus', 1) @@ -340,7 +423,7 @@ class FileAssetViewSet(CompanyScopedMixin, viewsets.ModelViewSet): asset_type = request.query_params.get('type', '') keyword = request.query_params.get('keyword', '') - qs = FileAsset.objects.all() + qs = self.get_queryset() if keyword: qs = qs.filter(FileName__icontains=keyword) @@ -551,6 +634,15 @@ class ItemReferenceViewSet(viewsets.ModelViewSet): item = ItemReference.objects.get(ItemUUID=item_bytes) except (ItemReference.DoesNotExist, ValueError): return Response({'code': 1, 'msg': '物品不存在'}, status=404) + # IDOR 防护:校验物品归属当前用户(管理员放行) + from gvsdsdk.auth.admin_service import IsAdmin + if not IsAdmin(request): + _pub = item.ItemPublisherUserUUID + _usr = request.user.UserUUID + _pub_b = _pub.bytes if hasattr(_pub, 'bytes') else _pub + _usr_b = _usr.bytes if hasattr(_usr, 'bytes') else _usr + if _pub_b != _usr_b: + return Response({'code': 403, 'msg': '无权操作他人物品'}, status=403) for field, val in data.items(): setattr(item, field, val) item.save() @@ -564,10 +656,19 @@ class ItemReferenceViewSet(viewsets.ModelViewSet): try: item_bytes = UUIDObj(item_uuid).bytes item = ItemReference.objects.get(ItemUUID=item_bytes) - item.delete() - return Response({'code': 0, 'msg': '删除成功'}) except (ItemReference.DoesNotExist, ValueError): return Response({'code': 1, 'msg': '物品不存在'}, status=404) + # IDOR 防护:校验物品归属当前用户(管理员放行) + from gvsdsdk.auth.admin_service import IsAdmin + if not IsAdmin(request): + _pub = item.ItemPublisherUserUUID + _usr = request.user.UserUUID + _pub_b = _pub.bytes if hasattr(_pub, 'bytes') else _pub + _usr_b = _usr.bytes if hasattr(_usr, 'bytes') else _usr + if _pub_b != _usr_b: + return Response({'code': 403, 'msg': '无权操作他人物品'}, status=403) + item.delete() + return Response({'code': 0, 'msg': '删除成功'}) class BackpackViewSet(CompanyScopedMixin, viewsets.ModelViewSet): @@ -605,6 +706,15 @@ class BackpackViewSet(CompanyScopedMixin, viewsets.ModelViewSet): bp = Backpack.objects.get(BackpackItemUUID=bp_bytes) except (Backpack.DoesNotExist, ValueError): return Response({'code': 1, 'msg': '背包物品不存在'}, status=404) + # IDOR 防护:校验物品归属当前用户(管理员放行) + # 统一转 bytes 比较,避免 UUIDObj 与 bytes 类型不一致导致误判 + from gvsdsdk.auth.admin_service import IsAdmin + if not IsAdmin(request): + _bp_user = bp.UserUUID_id + _req_user = request.user.UserUUID + _req_user_b = _req_user.bytes if hasattr(_req_user, 'bytes') else _req_user + if _bp_user != _req_user_b: + return Response({'code': 403, 'msg': '无权操作他人背包物品'}, status=403) for field, val in data.items(): setattr(bp, field, val) bp.save() @@ -622,6 +732,15 @@ class BackpackViewSet(CompanyScopedMixin, viewsets.ModelViewSet): bp = Backpack.objects.get(BackpackItemUUID=bp_bytes) except (Backpack.DoesNotExist, ValueError): return Response({'code': 1, 'msg': '背包物品不存在'}, status=404) + # IDOR 防护:校验物品归属当前用户(管理员放行) + # 统一转 bytes 比较,避免 UUIDObj 与 bytes 类型不一致导致误判 + from gvsdsdk.auth.admin_service import IsAdmin + if not IsAdmin(request): + _bp_user = bp.UserUUID_id + _req_user = request.user.UserUUID + _req_user_b = _req_user.bytes if hasattr(_req_user, 'bytes') else _req_user + if _bp_user != _req_user_b: + return Response({'code': 403, 'msg': '无权操作他人背包物品'}, status=403) if bp.ItemLength <= discard_len: bp.delete() return Response({'code': 0, 'msg': '物品已丢弃'}) @@ -654,7 +773,7 @@ class CashReceiptsPaymentsLedgerViewSet(CompanyScopedMixin, viewsets.ModelViewSe ser = FinanceShowSerializer(data=request.query_params or request.data) ser.IsValid(raise_exception=True) data = ser.ValidatedData - qs = CashReceiptsPaymentsLedger.objects.all() + qs = self.get_queryset() if 'StartDate' in data: qs = qs.filter(CashBookTransactionTime__gte=data['StartDate']) if 'EndDate' in data: @@ -669,16 +788,18 @@ class CashReceiptsPaymentsLedgerViewSet(CompanyScopedMixin, viewsets.ModelViewSe return Response({'code': 0, 'data': serializer.data}) -class AssetViewSet(CompanyScopedMixin, viewsets.ModelViewSet): +class AssetViewSet(UserScopedMixin, viewsets.ModelViewSet): queryset = Asset.objects.all() serializer_class = AssetSerializer permission_classes = [IsAuthenticated] + user_field = 'AssetUserUUID' -class LiabilityViewSet(CompanyScopedMixin, viewsets.ModelViewSet): +class LiabilityViewSet(UserScopedMixin, viewsets.ModelViewSet): queryset = Liability.objects.all() serializer_class = LiabilitySerializer permission_classes = [IsAuthenticated] + user_field = 'LiabilityUserUUID' class PositionTemplateViewSet(TenantScopedMixin, viewsets.ModelViewSet): @@ -689,7 +810,7 @@ class PositionTemplateViewSet(TenantScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = PositionTemplate.objects.all() + qs = self.get_queryset() if keyword: qs = qs.filter(TemplateName__icontains=keyword) serializer = self.get_serializer(qs[:20], many=True) @@ -715,7 +836,8 @@ class PositionViewSet(TenantScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): keyword = request.query_params.get('keyword', '') - qs = Position.objects.all() + # 使用 get_queryset() 自动应用租户隔离(管理员看全部,普通用户只看当前租户) + qs = self.get_queryset() if keyword: qs = qs.filter(PositionName__icontains=keyword) data = list(qs[:20].values('PositionUUID', 'PositionName')) @@ -734,9 +856,9 @@ class ShopViewSet(CompanyScopedMixin, viewsets.ModelViewSet): @action(detail=False, methods=['get']) def search(self, request): - from gvsdsdk.models import Shop as ShopModel keyword = request.query_params.get('keyword', '') - qs = ShopModel.objects.all() + # 使用 get_queryset() 自动应用公司隔离(管理员看全部,普通用户只看当前公司) + qs = self.get_queryset() if keyword: qs = qs.filter(ShopName__icontains=keyword) data = list(qs[:20].values('ShopUUID', 'ShopName')) diff --git a/gvsds/apps/ugm/__pycache__/views.cpython-312.pyc b/gvsds/apps/ugm/__pycache__/views.cpython-312.pyc index 6e9db8b9f6cb7e4097e4faaffda62f5092842c84..889ffb35513408f086237501baa5748080ee6afe 100644 GIT binary patch delta 3255 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 m<$g&WaO)ca_^lsaLA&>>$5&x8!PyG-76FJZT diff --git a/gvsds/apps/ugm/views.py b/gvsds/apps/ugm/views.py index 895c4984..3504c6a5 100644 --- a/gvsds/apps/ugm/views.py +++ b/gvsds/apps/ugm/views.py @@ -90,11 +90,12 @@ def _ClearJwtCookies(response): def _SetCurrentRoleCookie(response, user): """查询用户首个角色并设置 CurrentRoleUUID cookie""" from gvsds.apps.gerp.models import UserRole as _UserRole - first_role = _UserRole.objects.filter(UserUUID=user.UserUUID).select_related('RoleUUID').first() + # UserRole.RoleUUID 是 UUIDField,不能 select_related + first_role = _UserRole.objects.filter(UserUUID=user.UserUUID).first() if first_role and first_role.RoleUUID: max_age = int(settings.SIMPLE_JWT['REFRESH_TOKEN_LIFETIME'].total_seconds()) response.set_cookie( - 'CurrentRoleUUID', UUIDObj(first_role.RoleUUID.RoleUUID).str, + 'CurrentRoleUUID', UUIDObj(first_role.RoleUUID).str, domain=settings.SESSION_COOKIE_DOMAIN, max_age=max_age, secure=True, httponly=False, samesite='None', ) diff --git a/gvsds/auth.py b/gvsds/auth.py index 2d1eacbc..91f4232f 100644 --- a/gvsds/auth.py +++ b/gvsds/auth.py @@ -8,23 +8,94 @@ SimpleJWT 的 JWTAuthentication 在 __init__ 中调用 django.contrib.auth.get_u 同时支持从 Authorization 请求头和 HttpOnly Cookie 读取 JWT, 以兼容前端基于 Cookie 的认证方式(credentials: 'include')。 + +额外职责:JWT 认证成功后立即解析当前角色上下文(current_role / current_company_uuid / +current_tenant_uuid),填充到 request 对象。这解决了 Django 中间件在 DRF 认证之前运行、 +导致中间件无法获取已认证用户的问题。 """ +import logging +from django.core.exceptions import ObjectDoesNotExist from rest_framework_simplejwt.authentication import JWTAuthentication from gvsds.apps.core.models import User +_logger = logging.getLogger(__name__) + #: 存储 Access Token 的 Cookie 名称 JWT_ACCESS_COOKIE = 'AccessToken' #: 存储 Refresh Token 的 Cookie 名称 JWT_REFRESH_COOKIE = 'RefreshToken' +def ResolveRoleContext(request, user): + """解析当前角色上下文,填充 request.current_role / current_company_uuid / current_tenant_uuid + + 从 CurrentRoleUUID Cookie 读取角色 UUID,查询 UserRole + Role, + 设置 request.current_role / current_company_uuid / current_tenant_uuid。 + + Args: + request: HTTP 请求对象(会被原地修改) + user: 已认证的 User 实例 + """ + # 确保默认值(防止重复调用时残留旧值) + request.current_role = None + request.current_company_uuid = None + request.current_tenant_uuid = None + + if not user or not getattr(user, 'is_authenticated', False): + if hasattr(user, 'IsAuthenticated'): + if not user.IsAuthenticated: + return + else: + return + + from gvsdsdk.models import UserRole, Role + from gvsdsdk.model_utils import UUIDObj + + role_uuid_str = request.COOKIES.get('CurrentRoleUUID', '') + if not role_uuid_str: + _logger.warning('[ResolveRoleContext] 已认证用户 %s 无 CurrentRoleUUID cookie', user) + return + + try: + role_uuid_bytes = UUIDObj(role_uuid_str).bytes + user_role = UserRole.objects.filter( + UserUUID=user.UserUUID, + RoleUUID=role_uuid_bytes + ).first() + if not user_role: + _logger.warning('[ResolveRoleContext] cookie=%s 但 UserRole 未找到 (user=%s)', + role_uuid_str[:36], user) + return + # 单独查询 Role 实例(Role.TenantUUID 提供租户信息) + role = Role.objects.filter(RoleUUID=role_uuid_bytes).first() + if role: + request.current_role = role + # 从 Role 实例获取租户 UUID(UUIDObj,FormService 会转 bytes) + request.current_tenant_uuid = role.TenantUUID + # UserRole.CompanyUUID 是 UUIDField,直接赋值 + if user_role.CompanyUUID: + request.current_company_uuid = user_role.CompanyUUID + _logger.info('[ResolveRoleContext] cookie=%s, role=%s, tenant=%s, company=%s', + role_uuid_str[:36], + getattr(role, 'RoleName', None) if role else None, + request.current_tenant_uuid, + request.current_company_uuid) + except (ValueError, KeyError, ObjectDoesNotExist) as e: + _logger.warning('[ResolveRoleContext] cookie=%s 解析失败: %s', role_uuid_str[:36], e) + except Exception: + _logger.exception('[ResolveRoleContext] 解析当前角色失败') + + class CustomJWTAuthentication(JWTAuthentication): """JWT 认证——使用项目自定义 User 模型(core.User) 优先从 Authorization 请求头读取 token; 若请求头无 token,则回退到 HttpOnly Cookie(AccessToken)。 + + 认证成功后立即解析当前角色上下文(ResolveRoleContext), + 确保 request.current_role / current_company_uuid / current_tenant_uuid 可用。 """ def __init__(self, *args, **kwargs): @@ -47,3 +118,14 @@ class CustomJWTAuthentication(JWTAuthentication): def get_auth_header_types(self): from rest_framework_simplejwt.settings import api_settings return api_settings.AUTH_HEADER_TYPES + + def authenticate(self, request): + """JWT 认证成功后立即解析角色上下文""" + result = super().authenticate(request) + if result is None: + return None + user, validated_token = result + # 认证成功后立即解析角色上下文(DRF 会在 authenticate 返回后设置 request.user, + # 但此时我们已有 user 实例,可直接传入解析) + ResolveRoleContext(request, user) + return result diff --git a/gvsdsdk/__pycache__/model_utils.cpython-312.pyc b/gvsdsdk/__pycache__/model_utils.cpython-312.pyc index 88a54a79fc14cd8223386f705057260ff0ad75b9..ff05aff69777a3f23f4fb50191430bd3b73f47b6 100644 GIT binary patch delta 288 zcmdm?@LrMkG%qg~0}%ALcx7#$$UBQMYvPJjt`zPThA6&D9!=iOf{e47cobc%VuF1g z{T$t0V>Yj4Ucx?Eh)rzrVm29(My3?TV1^W?U0W$8f$oPcVI#ehTu!vj&t>5&s7FNkV&6kXsj+`tGVibR1@ewzG~L%7qJ zigYG#=ayqkn|zHsp6e4EBdh9XHYQf1%~m{znHbF`|KpPblCu0-toA@n?vs7^JKR95 zA|nt1GN?!xNc`fk$<0qG%}KQ@0$I)o#Kk5+;sY}yBjarb&f5&!cNyfrv)M5kePjR; KPE3=f1mpl9kxHom delta 152 zcmaE_xI=;WG%qg~0}%MHb;+`s$UBQMW#WodmP&3-p3UNnvzZvhHt%Cz!p`ic$v2si zCv9>Lk2GWAM_M&$LRv@#LQN|KL@+=LB2o(qQW$WNI#hzRHj>-euI@Pv zLn1LC#9G}yz={|c*!c%wVYO7LBLf47iB7!dl!)H&z0-ZZ-{J0!{n4?%SXQ2h+88|A z{bL;3FKFek|Ftr;;YZylad+Z<-|GY~gD7>Lav$&(t#@YHyyb;Kl+m?-cei`KmzLW6 zztCi@pPEy%&E5XK4_P#|{<(3JK2V7A=^a(6c|c&nQ@lbi7oNlK^lr)ATO20j29<-N^onW3$7(ZGiC-NAJ3h>2 z+pIxlJj({v@m$5GdX+l(Ty4@hM0Hu4m-GT2tF^k!W}(cT+N~skV7s6A>i{t{46L#9 zi)T&T5SjXEom^VGv*mVLk6RDdw{VkP$u9{i&*9hcElZrAgcJwLFG7rga%42|Gh5JC zzUeDR#!WF_#QWOoIT?9RU|BS12strU!`SQh{CjW>kF|2qC@8P=-{s4+Lo?T3urQJ2 delta 573 zcmZ3_-^RyxnwOW00SHdq_Q-n2K9MhtQD|c6naQb)BFyZb#gmUQx-q?C+|0+s!sw8# z!N5=?n!=RAox_>SRm;lAP{Ufn3epS$t05Fa3YQI#Rl^$0P{WYI1J}cw!##Npv$P}+ zSe6Ye%LkX`2g+VxmbT{u%d&%I1>mxRIo!E?wH!eEII!3!0M^41%upgf`5SY7AWIPs z&;dofAc7A>@B;}=##`J4sYSV&#l@NVdBsHnU@HDVN-?8vGeD-M*nC6!)QoKl>Ut(RC@lA)KFlADqGGUZU~9nU-{NyDDJjZKDlJKMEh@?{(&U`HmQ_j_0ssI2 diff --git a/gvsdsdk/auth/__pycache__/admin_service.cpython-312.pyc b/gvsdsdk/auth/__pycache__/admin_service.cpython-312.pyc index 541128a20cd55ab1b2d5f0828b834d7476aa9d90..81a9d68a4a3d9ce603fb7026ec6b78ce99da6c99 100644 GIT binary patch literal 5518 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 AqW}N^ diff --git a/gvsdsdk/auth/__pycache__/middleware.cpython-312.pyc b/gvsdsdk/auth/__pycache__/middleware.cpython-312.pyc index c8ee519c80a202dd343195a80a25b8bee28572c2..a370565bf3a15e05e45409b70970a3a9e73a3e38 100644 GIT binary patch delta 1291 zcmdlWc2|b)G%qg~0}yC5cx5T`ZsZGO=214GBvCz94QQ|LCyezJjN6Tpd(l&E3k>wXE8wp zk;&Dp5FR5#o(htM+Q@1k>=aJ8g;;$Bvl3{M0#=js;bx}rU=NtpPzN$H)G%b31Kq4x zuLILmq6cRgz*uR_Dhx#`wQN|egDGN2;etDy4dKX0h7^WcwmP;R7YJR>SYiaz0yI^E zkpX0?2}CH%6efkBQ@B?%LU@b}kqkZQ3i2mmb6zZ8 z{A}KYr%N`z*s|10LA7}DOEzPlQdc~BpKjmuV%?G#>l>d>S@Eo6J5Z0pv*`;Ikb)sJ z)YAp(e@&Uma_n2=*>dvJ(^HFXv1I0@<=GfFOIlw1)g zy}>VhonPUi9lyc?@yq<07dSM3eBot~R9@n7MbF`axZ@`VM!^EcJ0eok{U-WdP!7Kk znRX#P>q17>g~;p+xp~(`@-K?y|DL>oQ$a}{7z78^U6kEy7>?P9cnC8e7m#w-XFjg4 z2xOlSV)PK6{D#w<(P**?mjR>ZO=F;zB2d~Y0u@e0<{*L(MDPHKUmP~M`6;D2 Wsdh!SlRt48@|rPfePRF-U?l(|1y4T! delta 475 zcmcaBvq6mSG%qg~0}#Af>5}!8b0c3MGo#exZ01aER-hm=5P!bKH2FTWJ0rtnM;0-r zqAx&7i>c_|me&^DnqSVQ<9Ga}9dO)R3_t>`1OPGaZqon& diff --git a/gvsdsdk/auth/__pycache__/permissions.cpython-312.pyc b/gvsdsdk/auth/__pycache__/permissions.cpython-312.pyc index 52976886b997adfec1fd474d64c911a4109b4cb3..b1e544f04b70a999be559710fa7c804755bbc3a6 100644 GIT binary patch delta 5764 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 diff --git a/gvsdsdk/auth/admin_service.py b/gvsdsdk/auth/admin_service.py index 6b7d261c..5e1d3251 100644 --- a/gvsdsdk/auth/admin_service.py +++ b/gvsdsdk/auth/admin_service.py @@ -1,24 +1,31 @@ """gvsdsdk.auth.admin_service — 管理员身份统一判定服务 -提供单一的 ``IsAdmin`` 函数,作为所有管理员特权判断的统一入口。 -消除 gerp 各视图中重复实现的 ``_is_admin`` / ``_IsAdminUser`` / ``IsAdmin`` 等函数, -统一 ``admin:super`` 权限查询逻辑。 +提供管理员身份判定的统一入口,基于两个层级的管理员权限码: -判定顺序: + ``admin:super`` —— 跨租户管理员(如 GERP 测试员),关闭租户隔离 + 公司隔离 + ``admin:tenant`` —— 租户内管理员(如 集团超级管理员),仅关闭公司隔离,保留租户隔离 + +判定顺序(适用于所有判定函数): 1. 用户未认证 → False - 2. IsSuperuser 或 IsStaff → True(系统级管理员) - 3. 当前角色拥有 ``admin:super`` 权限 → True(租户级管理员) + 2. 有当前角色 → 仅依据该角色的权限码判定(角色超级管理员模式) + 超级用户切换到普通角色时也应用隔离,不再因 IsSuperuser/IsStaff 直接放行 + 3. 无当前角色 → IsSuperuser 或 IsStaff 作为系统维护回退(仅授予 admin:tenant 级别, + 不授予 admin:super,防止超级用户在未切换角色时获得跨租户越权能力) 4. 否则 → False +提供的函数: + ``IsAdmin`` —— 是否为管理员(admin:super 或 admin:tenant),用于权限校验 + ``IsSuperAdmin`` —— 是否为跨租户管理员(仅 admin:super),用于租户隔离判定 + ``IsTenantAdmin``—— 是否为租户内管理员(仅 admin:tenant),用于租户内管理员判定 + 用法: - from gvsdsdk.auth.admin_service import IsAdmin + from gvsdsdk.auth.admin_service import IsAdmin, IsSuperAdmin if IsAdmin(request): - ... + ... # 权限校验放行 - # 或无 request 场景(如 service 层) - if IsAdmin(user=user, role_uuid=role_uuid_bytes): - ... + if IsSuperAdmin(request): + ... # 关闭租户隔离 """ from __future__ import annotations @@ -39,14 +46,62 @@ def _IsAuthenticated(user) -> bool: return False -def IsAdmin(request=None, user=None, role_uuid=None) -> bool: - """判断用户是否为管理员(系统级或租户级) +def _HasPerm(request=None, user=None, role_uuid=None, perm_code=None) -> bool: + """判断当前角色是否拥有指定权限码 + 内部函数,供 IsAdmin / IsSuperAdmin / IsTenantAdmin 复用。 判定顺序: 1. 用户未认证 → False - 2. ``IsSuperuser`` 或 ``IsStaff`` → True - 3. 当前角色拥有 ``admin:super`` 权限 → True + 2. 有当前角色 → 仅依据该角色的指定权限码判定(角色超级管理员模式) + 3. 无当前角色 → IsSuperuser 或 IsStaff 作为系统维护回退(仅授予 admin:tenant 级别, + perm_code='admin:super' 时直接返回 False,防止跨租户越权) 4. 否则 → False + """ + if perm_code is None: + return False + + # 1. 解析 user + if request is not None and user is None: + user = getattr(request, 'user', None) + if not _IsAuthenticated(user): + return False + + # 2. 解析 role_uuid(优先从参数,其次从 request.current_role) + if role_uuid is None and request is not None: + current_role = getattr(request, 'current_role', None) + if current_role: + role_uuid = getattr(current_role, 'RoleUUID', None) + + # 3. 有当前角色时,仅依据该角色的指定权限码判定(角色超级管理员模式) + if role_uuid: + try: + from gvsdsdk.models import RolePermission, Permission + perm_uuids = Permission.objects.filter( + PermCode=perm_code, + PermStatus=1, + ).values_list('PermUUID', flat=True) + return RolePermission.objects.filter( + RoleUUID=role_uuid, + PermUUID__in=perm_uuids, + ).exists() + except Exception: + _logger.warning('查询权限 %s 失败', perm_code, exc_info=True) + return False + + # 4. 无当前角色时,IsSuperuser/IsStaff 作为系统维护回退(无角色无法做权限判定) + # 安全策略:无角色时仅授予 admin:tenant 级别权限(不授予 admin:super), + # 防止超级用户在未切换角色时获得跨租户越权能力。 + # superuser 无角色 → IsAdmin=True / IsTenantAdmin=True / IsSuperAdmin=False + if perm_code == 'admin:super': + return False + return bool(getattr(user, 'IsSuperuser', False) or getattr(user, 'IsStaff', False)) + + +def IsAdmin(request=None, user=None, role_uuid=None) -> bool: + """判断用户是否为管理员(admin:super 或 admin:tenant) + + 用于权限校验:两种层级的管理员都应放行。 + 跨租户管理员(admin:super)和租户内管理员(admin:tenant)都是管理员。 参数: request: 请求对象。优先从 ``request.user`` 取用户、``request.current_role`` 取角色。 @@ -57,38 +112,22 @@ def IsAdmin(request=None, user=None, role_uuid=None) -> bool: 返回: bool """ - # 1. 解析 user - if request is not None and user is None: - user = getattr(request, 'user', None) - if not _IsAuthenticated(user): - return False - - # 2. 系统级管理员(超级用户/员工) - if getattr(user, 'IsSuperuser', False) or getattr(user, 'IsStaff', False): + if _HasPerm(request, user, role_uuid, 'admin:super'): return True + return _HasPerm(request, user, role_uuid, 'admin:tenant') - # 3. 解析 role_uuid - if role_uuid is None and request is not None: - current_role = getattr(request, 'current_role', None) - if current_role: - role_uuid = getattr(current_role, 'RoleUUID', None) - if not role_uuid: - return False - # 4. 租户级管理员(当前角色拥有 admin:super 权限) - # 注意:gvsdsdk.models.RolePermission 的 RoleUUID/PermUUID 都是 UUIDField(非 ForeignKey), - # 不能用 RoleUUID_id= 或 PermUUID__PermCode 跨表查询。 - # 改用 subquery + 直接 UUID 匹配。 - try: - from gvsdsdk.models import RolePermission, Permission - admin_super_uuids = Permission.objects.filter( - PermCode='admin:super', - PermStatus=1, - ).values_list('PermUUID', flat=True) - return RolePermission.objects.filter( - RoleUUID=role_uuid, - PermUUID__in=admin_super_uuids, - ).exists() - except Exception: - _logger.warning('IsAdmin 查询 admin:super 权限失败', exc_info=True) - return False +def IsSuperAdmin(request=None, user=None, role_uuid=None) -> bool: + """判断用户是否为跨租户管理员(仅 admin:super) + + 用于隔离控制:仅 admin:super 关闭租户隔离,可跨租户查看数据。 + """ + return _HasPerm(request, user, role_uuid, 'admin:super') + + +def IsTenantAdmin(request=None, user=None, role_uuid=None) -> bool: + """判断用户是否为租户内管理员(仅 admin:tenant) + + 用于租户内管理员判定:仅关闭公司隔离,保留租户隔离。 + """ + return _HasPerm(request, user, role_uuid, 'admin:tenant') diff --git a/gvsdsdk/auth/middleware.py b/gvsdsdk/auth/middleware.py index e0fa04fd..6ef90355 100644 --- a/gvsdsdk/auth/middleware.py +++ b/gvsdsdk/auth/middleware.py @@ -46,9 +46,18 @@ class CurrentRoleMiddleware: # UserRole.CompanyUUID 是 UUIDField,直接赋值 UUID bytes if user_role.CompanyUUID: request.current_company_uuid = user_role.CompanyUUID - except (ValueError, KeyError, ObjectDoesNotExist): - pass + logger.info('[CurrentRoleMiddleware] cookie=%s, role=%s, tenant=%s, company=%s', + role_uuid_str[:36], + getattr(role, 'RoleName', None) if role else None, + request.current_tenant_uuid, + request.current_company_uuid) + else: + logger.warning('[CurrentRoleMiddleware] cookie=%s 但 UserRole 未找到 (user=%s)', + role_uuid_str[:36], request.user) + except (ValueError, KeyError, ObjectDoesNotExist) as e: + logger.warning('[CurrentRoleMiddleware] cookie=%s 解析失败: %s', role_uuid_str[:36], e) except Exception: logger.exception('CurrentRoleMiddleware 解析当前角色失败') - + else: + logger.warning('[CurrentRoleMiddleware] 已认证用户 %s 无 CurrentRoleUUID cookie', request.user) return self.get_response(request) diff --git a/gvsdsdk/auth/permissions.py b/gvsdsdk/auth/permissions.py index 7546abd9..25daf9d0 100644 --- a/gvsdsdk/auth/permissions.py +++ b/gvsdsdk/auth/permissions.py @@ -8,31 +8,40 @@ _logger = _logging.getLogger(__name__) def IsAdminScopeDisabled(request): - """判断是否为管理员特权模式(X-Admin-Verify 头或 admin_verify=1 且用户是管理员),此时关闭隔离 + """判断是否为管理员特权模式(当前角色拥有 admin:super 或 admin:tenant 权限),此时关闭公司隔离 - 安全:优先从请求头 ``X-Admin-Verify`` 读取(避免 URL 泄漏到访问日志/浏览器历史/Referer), - 兼容旧版 query 参数 ``admin_verify=1`` 以保证前端渐进迁移。 - - 管理员判定委托给 ``gvsdsdk.auth.admin_service.IsAdmin``,统一 ``admin:super`` 查询逻辑。 + 与 FormService 行为一致:管理员自动关闭公司隔离,无需前端传 admin_verify=1。 + 管理员判定委托给 ``gvsdsdk.auth.admin_service.IsAdmin``,统一权限查询逻辑。 """ if not request: return False - # 优先从请求头读取(更安全),兼容 query 参数 - admin_verify = ( - request.headers.get('X-Admin-Verify') == '1' - or ( - hasattr(request, 'query_params') - and request.query_params.get('admin_verify') == '1' - ) - ) - if not admin_verify: - return False from gvsdsdk.auth.admin_service import IsAdmin return IsAdmin(request) +def IsTenantScopeDisabled(request): + """判断是否为跨租户管理员特权模式(当前角色拥有 admin:super 权限),此时关闭租户隔离 + + 仅 admin:super(跨租户管理员)可关闭租户隔离,admin:tenant(租户内管理员)保留租户隔离。 + 管理员判定委托给 ``gvsdsdk.auth.admin_service.IsSuperAdmin``。 + """ + if not request: + return False + from gvsdsdk.auth.admin_service import IsSuperAdmin + return IsSuperAdmin(request) + + class IsAdminUser(BasePermission): - """仅允许管理员(IsStaff 或 IsSuperuser)访问""" + """仅允许管理员(当前角色拥有 admin:super 或 admin:tenant 权限)访问 + + 两级管理员体系: + - admin:super:跨租户管理员(GERP 测试员),关闭租户隔离 + 公司隔离 + - admin:tenant:租户内管理员(集团超级管理员),仅关闭公司隔离,保留租户隔离 + + 依据当前角色的权限判定,超级用户切换到普通角色时不再自动放行, + 需切换到拥有 admin:super 或 admin:tenant 权限的角色。 + 无当前角色时回退到 IsSuperuser/IsStaff(系统维护场景)。 + """ def has_permission(self, request, view): user = request.user @@ -40,7 +49,8 @@ class IsAdminUser(BasePermission): return False if not isinstance(user, _GetUserModel()): return False - return user.IsStaff or user.IsSuperuser + from gvsdsdk.auth.admin_service import IsAdmin + return IsAdmin(request) class IsCompanyMember(BasePermission): @@ -99,13 +109,17 @@ class TenantScopedMixin: """ViewSet Mixin:自动按当前角色所属租户过滤 queryset 用于纯租户级表(如 Tenant/Role/CompanyInfo),这些表无 CompanyUUID 字段, - 只能按 TenantUUID 过滤。当 URL 带 admin_verify=1 且用户为管理员时关闭隔离。 + 只能按 TenantUUID 过滤。仅跨租户管理员(admin:super)关闭租户隔离, + 租户内管理员(admin:tenant)保留租户隔离。 """ tenant_field = 'TenantUUID' # 可在 ViewSet 中覆盖 def _is_admin_scope_disabled(self): - """判断是否为管理员特权模式(委托给模块级 IsAdminScopeDisabled)""" - return IsAdminScopeDisabled(getattr(self, 'request', None)) + """判断是否为跨租户管理员特权模式(委托给模块级 IsTenantScopeDisabled) + + 仅 admin:super 关闭租户隔离,admin:tenant 保留租户隔离。 + """ + return IsTenantScopeDisabled(getattr(self, 'request', None)) def get_queryset(self): qs = super().get_queryset() @@ -124,6 +138,43 @@ class TenantScopedMixin: serializer.save() +class UserScopedMixin: + """ViewSet Mixin:自动按当前用户过滤 queryset(用户级数据隔离) + + 用于无 CompanyUUID/TenantUUID 字段的用户级表(如 Backpack/Message/WorkOrder/Asset/Liability), + 普通用户只能看自己的数据,管理员(admin:super 或 admin:tenant)看全部。 + + 使用方式: + class BackpackViewSet(UserScopedMixin, viewsets.ModelViewSet): + user_field = 'UserUUID' # FK 字段名(自动追加 _id)或 UUIDField 名 + """ + user_field = 'UserUUID' # 可在 ViewSet 中覆盖 + + def _is_admin_scope_disabled(self): + """判断是否为管理员特权模式(委托给模块级 IsAdminScopeDisabled)""" + return IsAdminScopeDisabled(getattr(self, 'request', None)) + + def get_queryset(self): + qs = super().get_queryset() + if self._is_admin_scope_disabled(): + return qs + user = getattr(self.request, 'user', None) if hasattr(self, 'request') else None + if not user or not getattr(user, 'is_authenticated', False): + return qs.none() + user_uuid = getattr(user, 'UserUUID', None) + if user_uuid is None: + return qs.none() + # 统一转 bytes(UUIDObj → bytes) + user_uuid = user_uuid.bytes if hasattr(user_uuid, 'bytes') else user_uuid + # 判断字段类型:ForeignKey 用 _id 后缀,UUIDField 直接用字段名 + fk_attr = self.user_field + '_id' + if hasattr(qs.model, fk_attr): + qs = qs.filter(**{fk_attr: user_uuid}) + elif hasattr(qs.model, self.user_field): + qs = qs.filter(**{self.user_field: user_uuid}) + return qs + + _PERM_CACHE_KEY = '_gvsdsdk_perm_verifier_cache' _ROLE_INACTIVE = object() # 哨兵:角色未启用,恒拒绝 @@ -133,7 +184,7 @@ def PermissionVerifier(permission, attributes, user=None, request=None): RBAC 仅检查当前角色(CurrentRoleUUID Cookie 指定的角色)的权限, 通过 RolePermission → Permission 链路查询,按当前公司过滤。 - 超级管理员/员工直接放行。 + 管理员(当前角色拥有 admin:super 权限)直接放行。 性能优化:ABAC 权限标识集合与 RBAC 权限码集合在首次调用时预取, 并挂载到 request 对象上作为请求级缓存,后续同一请求内的多次调用 @@ -146,8 +197,10 @@ def PermissionVerifier(permission, attributes, user=None, request=None): if not isinstance(user, _GetUserModel()): return False - # 超级管理员和员工直接放行 - if user.IsSuperuser or user.IsStaff: + # 管理员(当前角色拥有 admin:super 权限)直接放行 + # 采用角色超级管理员模式:超级用户切换到普通角色时也需通过 RBAC 校验 + from gvsdsdk.auth.admin_service import IsAdmin + if IsAdmin(request=request, user=user): return True current_role = getattr(request, 'current_role', None) if request else None diff --git a/gvsdsdk/model_utils.py b/gvsdsdk/model_utils.py index c7b157c1..07dc0029 100644 --- a/gvsdsdk/model_utils.py +++ b/gvsdsdk/model_utils.py @@ -63,6 +63,10 @@ class UUIDObj: def __bool__(self): return self._raw is not None + def __len__(self): + # UUID 固定 16 字节;空值返回 0 + return 16 if self._raw is not None else 0 + @lru_cache(maxsize=1) def _GetUserModel():