Files
danyang-match/docs/audits/matchmaker-permission-gap-audit.md
T
WorkBuddy 2d60f7899f docs(audit): ZC-MM-001 红娘角色权限差距审计报告
- 基准:《善缘丹阳 V1.1 红娘角色权限正式决策与开发规则》(DEC-MATCHMAKER-001~006
  全部 LOCKED,A1/B1 已确认)
- 审计范围:backend matchmaker/referral/audit/security/auth、Flyway V001~V052、
  admin-web 红娘页面、现有测试、治理文档;全部结论带文件:行号证据
- 核心结论:A1 分配关系未建模(分配=工单指派)、claim/followup/complete 无归属校验、
  /pending 全局队列对全体红娘可见、无红娘组长角色、模块内越权拒绝不落审计
- P0×5 / P1×6 / P2×6 差距清单 + 可复用模块(CounselingService INV-012 范式、
  DataScopeGuard、recordSensitiveAccess)+ ZC-MM-002~007 实施顺序建议
- 首次建立 docs/audits/ 目录
2026-10-09 22:46:32 +08:00

255 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 红娘角色权限差距审计(ZC-MM-001)
> **审计基准**:《善缘丹阳 V1.1 红娘角色权限正式决策与开发规则》V1.1(DEC-MATCHMAKER-001~006 全部 LOCKED,A1/B1 已确认)
> **审计日期**:2026-10-09
> **审计执行**:架构师(高见远)· software-danyang-mm 团队 · 只读走查,未修改任何业务代码
> **审计方式**:backend matchmaker/referral/audit/security/auth 模块、Flyway V001~V052、admin-web 红娘相关页面、现有测试与治理文档。所有结论均给出文件路径+行号或 SQL/代码证据。
---
## 0. 审计范围与方法
- 后端:`backend/src/main/java/com/danyang/match/platform/` 下 `matchmaker/`(controller 2、service 2、entity 3、mapper 3)、`referral/`(controller 1、service 1)、`audit/`、`shared/security/`、`auth/`、`counseling/`(对照 INV-012)、`review/`(对照敏感访问日志范式)。
- 数据库:`backend/src/main/resources/db/migration/` V001~V052 全量中 matchmaker 相关 8 个迁移(V001/V002/V009/V019/V021/V037/V038/V044/V052)。
- 前端:`admin-web/src/modules/matchmaker/MatchmakerManagementView.vue`、`modules/referral/ReferralManagementView.vue`、`router/index.ts`、`layouts/MainLayout.vue`。
- 测试:`backend/src/test` 中 matchmaker/referral/权限相关 6 个测试类。
- 治理文档:`docs/governance/decision-register.md`、`open-questions.md`、`docs/PENDING_DECISIONS.md`。
- 全局检索:`matchmaker_assignments`、`ServiceAssignment`、`matchmaker_leader`、`ContactAuthorization` 等关键词。
状态标注:【已实现】/【部分实现】/【缺失】/【无法确定】。
---
## 1. 现状总览
**一句话结论**:当前仓库的"红娘"只是一个挂在 `matchmaker_services` 服务单上的 `assigned_operator_id` 工单负责人概念——没有独立的服务对象分配关系、没有红娘组长角色、没有统一的数据范围/字段级隐私策略层;工单维度上已做过多轮越权修补(推荐/转介/详情归属校验),但**认领、跟进、完结等写接口仍缺归属校验,未分配队列与全局统计对全体红娘可见,审计与权限回收存在系统性缺口**。A1 核心未建模,B1 现状上"侥幸安全"(接口本就不返回联系方式)而非"设计安全"。
### 决策要求 vs 现状对照表
| # | 决策要求 | 现状 | 状态 | 关键证据 |
|---|---------|------|------|---------|
| 1 | DEC-002/A1:服务对象由平台授权人员或红娘组长分配,普通红娘仅访问本人有效分配对象 | 分配仅体现在 `matchmaker_services.assigned_operator_id`(服务单指派),无独立分配实体、无 assigned_by/理由/有效期/结束语义;`/pending` 全局队列对任何 `matchmaker.view` 持有者可见 | **缺失(核心)** | V009 L5-20;`MatchmakerOperatorController` L32-36;`MatchmakerServiceMapper.findSubmitted()` |
| 2 | DEC-002 规则2:禁止普通红娘认领未分配/他人对象 | `POST /operator/matchmaker/services/{id}/claim` 任何 `matchmaker.service` 持有者可对任何 assigned 服务接单,无归属校验 | **缺失** | `MatchmakerOperatorController` L49-56;`MatchmakerServiceService.claimService` L109-122 |
| 3 | DEC-002 规则3/6:分配、转分配、撤销、结束留痕;状态机覆盖 | 仅有 assign/complete/cancel(cancel 无 HTTP 入口,死代码);无转分配、无撤销、无结束原因;分配人(assigned_by)不记录 | **缺失** | `MatchmakerServiceService` L87-104、L229-237;V009 L11-14 |
| 4 | DEC-003/B1:默认平台内联系,不暴露完整手机号/微信号/详细地址 | 红娘侧 DTO/VO 与前端页面均无 phone/wechat/address/idNumber 字段;但也**不存在**任何联系授权机制(无表、无策略类) | **部分实现(结果安全、机制缺失)** | `MatchmakerServiceService.getServiceDetail` L329-348;`ReferralService` 全文;全局检索 ContactAuthorization 无结果 |
| 5 | DEC-004:红娘不得代替用户 Like/Pass/确认开启聊天/接受活动意向 | 设计与实现均贯彻"推荐≠Like":推荐仅建 `pending_view` 记录,Like/Pass 由用户本人端点(UserPrincipal+`findByIdAndUser` 归属校验)触发;转介确认须用户本人 | **已实现** | `MatchmakerService` L97-142、L213-258;`ReferralService.confirmReferral` L139-184;测试 `MatchmakerReferralPhase13Test` L132-296 |
| 6 | DEC-005:最小必要 + 数据范围 + 字段隐私 + 有效分配 | 无 Policy 层;数据范围校验散落且口径不一:recommend/转介有归属校验,claim/followup/complete 无;`matchmaker.assign` 与 `matchmaker.service` 语义即"工单指派/工单执行",不是"分配服务对象" | **缺失** | `MatchmakerServiceService` L142-151(有)vs L109-122、L186-203、L208-224(无) |
| 7 | DEC-006:全程留痕(分配、敏感查看、越权拒绝等) | 有 audit 基础设施且授权拒绝有全局监听;但 claim 不审计、敏感详情查看不记 SensitiveAccessLog、模块内归属拒绝不走 DataScopeGuard、分配审计不记 assigned_by/理由 | **部分实现** | `AuthorizationAuditListener` L25-49;`DataScopeGuard` 使用方仅 activity/counseling/service_appointment;`MatchmakerServiceService.saveAuditLog` L395-403(before/after 恒 null) |
| 8 | 功能权限矩阵:红娘无全站检索/导出/敏感字段 | matchmaker 角色仅授 3 权限(view/service/assign),无 user.view、无 data.export;全仓未发现导出端点 | **已实现(现状合规)** | V052 L24-28;grep `data.export` 使用方为 0 |
| 9 | 红娘组长角色 | 全仓(迁移+代码+前端)无 matchmaker_leader/team_lead | **缺失** | grep 无结果 |
| 10 | 测试基线 MM-AUTH/CONTACT/ACTION/PRIVACY/AUDIT | 仅有"红娘不代替用户操作"(MM-ACTION 方向)与状态白名单守卫测试;无数据范围/联系方式/审计/生命周期场景测试 | **缺失** | `MatchmakerReferralPhase13Test`、`MatchmakerServiceTest`(仅常量白名单校验) |
---
## 2. 分项审计
### 2.1 当前红娘角色及权限实现
**角色与权限定义**【部分实现】:
- `matchmaker` 角色定义于 V001:`('00000000-0000-0000-0000-000000000005', 'matchmaker', '公益红娘', '提供人工婚恋服务', TRUE)`(V001 L145)。
- 三条红娘权限定义于 V001 L161-163:`matchmaker.view`(020)、`matchmaker.assign`(021)、`matchmaker.service`(022)。
- **角色-权限授权缺口曾真实存在**:V052 迁移注释自证——"创建红娘运营账号后发现该角色在 role_permissions 中零条授权,登录后台 403"(V052 L4-12);V052 仅补授 3 条权限(L24-28)。
- 其余角色授权同样残缺:V044 注释承认"permissions 共 48 条,role_permissions 里 super_admin 只有 18 条;matchmaker 角色为 0 条"(V044 L5-11),V044 只回补了 super_admin,**platform_admin/organization_admin 等角色的红娘权限授予状态无法从迁移中确认**【无法确定,需查实际库】。注意 `PermissionMatrixTest` L57-59 期望 platform_admin 拥有 matchmaker.view/assign/service,但迁移中未见对应 INSERT【测试期望与 DB 授权可能漂移】。
- **红娘组长角色不存在**:迁移与代码 grep `leader|team_lead` 均无结果。
**后端接口面**【部分实现】:
- 运营端 `MatchmakerOperatorController`(`/operator/matchmaker`,类级 `@PreAuthorize("hasAuthority('matchmaker.view')")` L19):dashboard(L25)、pending(L32)、assign(L38,`matchmaker.assign`)、claim(L49,`matchmaker.service`)、my-services(L58)、recommendations(L69)、services/{id} 详情(L75)、recommend(L87)、followup(L99)、complete(L112)。
- 用户端 `MatchmakerController`(`/matchmaker`):提交申请(L48-58)、我的服务(L60)、推荐列表(L67)、viewed/accept/pass(L73-96)。
- 转介 `ReferralController`(`/referrals`):service-station(L33)/activity(L53) 均 `matchmaker.service`;confirm(L70)/my-history(L85) 用户本人;operator/history(L99) 按 `matchmaker.service`。
**关键发现:`matchmaker.assign` 的语义**——它是"把服务工单指派给某红娘"(`assignService(serviceId, operatorId, organizationId)`,`MatchmakerServiceService` L87-104),即**工单分派**而非决策文档意义上的"服务对象分配"(对用户建立服务关系)。当前系统里"服务对象"就是服务单上的 `user_id`,分配关系与工单合一,无法表达"同一用户多次/多任务分配、分配历史、到期、撤销"。
**已知实现缺陷(非权限但影响验收)**:
- `countInProgressByOperator` 统计 `status='processing'`(`MatchmakerServiceMapper` L28-30),而 DB CHECK 白名单只有 `in_service`(V009 L30-32),工作台 inProgress 恒为 0。
- `cancelService`(`MatchmakerServiceService` L229-237)无任何 Controller 入口,死代码。
### 2.2 服务对象分配建模
**结论:未建模**【缺失】。
- 全仓(backend main + admin-web src + 全部迁移)grep `matchmaker_assignments|service_assignment|ServiceAssignment` 无结果。
- 唯一的"分配"载体是 `matchmaker_services.assigned_operator_id`(V009 L12),字段只有 `assigned_operator_id / assigned_at / organization_id`,**没有** assigned_by、assignment_reason、assignment_scope、expires_at、ended_at、end_reason、status(分配关系状态与服务单状态混用)。
- `/operator/matchmaker/my-services` 返回该红娘名下全部服务含历史(`MatchmakerServiceMapper.findByOperatorId` L36-38,`MatchmakerServiceService.getMyServices` L68-82),即"我的服务对象 = 工单负责人",非独立分配关系。
- **认领入口存在**:`POST /services/{id}/claim` 仅需 `matchmaker.service`(`MatchmakerOperatorController` L49-56),`claimService` 只校验状态=assigned,不校验该单是否分配给本人,也不把 assigned_operator_id 改成自己(`MatchmakerServiceService` L109-122)——任何红娘可以把任何红娘的 assigned 单推进到 in_service。直接违反 DEC-002 规则 2(不能自行认领)。
- **转分配/撤销/结束**:无 transfer/revoke 接口;`cancelService` 无入口;`completeService` 无归属校验(L208-224)。
### 2.3 列表/搜索/详情/导出数据范围一致性
| 接口 | 数据范围 | 判定 |
|------|---------|------|
| `GET /pending`(L32-36 → `findSubmitted` Mapper L17-18) | **全局**:任何 `matchmaker.view` 持有者可见全库 submitted 申请,含申请人 userId + 需求描述(Service L245-258) | ❌ 违反 DEC-002 规则1(不能浏览未分配对象库)、DEC-005 |
| `GET /dashboard`(L25-30 → L260-276) | pendingCount/todayCount/completedCount 为**全局统计**,inProgress 按本人 | ❌ 信息泄露(全局量级)+ 状态口径 bug |
| `GET /my-services`(L58-63 → `findByOperatorId`) | 按本人过滤 | ✅ |
| `GET /recommendations`(L69-73 → `findByOperatorId`) | 按本人过滤 | ✅ |
| `GET /services/{id}` 详情(L75-85 → `getServiceDetail` L301-351) | 负责人→完整(含 userProfile 8 字段 + followup 正文);非负责人→**仍返回**状态、assignedOperatorId、以及**该服务的推荐列表(含他人服务的 targetUserId 与推荐理由)**(L317-327 对所有调用者装配) | ⚠️ 部分修复:比 Phase 13 D4 前好,但非负责人仍可探测任意 serviceId 的存在性与他人推荐内容(违反"响应不得泄露目标用户是否存在");推荐理由是红娘对候选对象的自由文本,可能含隐私 |
| `POST /services/{id}/recommend`(L87-97 → L142-181) | 归属校验(L148-151)+ 不能推给申请人(L153-155) | ✅(但推荐对象 targetUserId 由红娘手输,无候选来源,见 2.4) |
| `POST /followup`(L99-110 → L186-203) | **无归属校验**:任何 `matchmaker.service` 持有者可对任意 serviceId 写跟进正文 | ❌ 违反 DEC-005;跟进正文为自由文本敏感写面 |
| `POST /complete`(L112-118 → L208-224) | **无归属校验** | ❌ |
| `POST /claim`(L49-56 → L109-122) | **无归属校验** | ❌(见 2.2) |
| 转介 `POST /referrals/*` | `assertMatchmakerServesUser`(`ReferralService` L45-52 → `findActiveByOperatorAndUser` Mapper L48-54)要求存在本人名下、该用户的进行中服务 | ✅(Phase 13 D5 修复,注释自证修复前可代任意用户创建报名/预约) |
| 导出 | 全仓未发现任何导出端点(grep `data.export|export` 在 main 源码无结果) | ✅ 现状无导出面 |
按 userId/查询参数越权形态:matchmaker 角色无 `user.view`(V052 仅 3 权限),无法触达用户管理接口,全站用户检索面关闭;但上述服务单维度参数越权真实存在。
### 2.4 联系方式暴露(B1)
**现状结果层面安全**【已实现(被动)】:
- 红娘侧所有返回 DTO 均为手工 Map 装配:服务详情 userProfile 仅 displayName/gender/age/regionName/educationCode/occupationCode/maritalStatus(`MatchmakerServiceService` L333-343),无手机号/微信号/详细地址/身份证。
- 前端 `MatchmakerManagementView.vue` 表格列仅 userId/serviceType/description/status/createdAt(L7-25)与推荐 userId/targetUserId/reason/status(L29-39);`ReferralManagementView.vue` 无联系方式字段。grep phone/mobile/wechat/身份证/地址 在两个 view 中均无命中。
- 转介活动时的 `emergencyContactPhoneEnc` 是**红娘录入**的紧急联系人信息(`ReferralController` L62、`ReferralService` L111-112),非读取用户联系方式。
- 无导出接口可绕道获取联系方式(2.3)。
**机制层面缺失**【缺失】:
- 全仓无 ContactAuthorization/contact_authorization/线下联系 相关表、实体、策略(grep 无结果)——DEC-003 要求的"电话/线下联系须专项授权"完全没有机制承载,目前是"没有功能所以没有漏洞"。若后续有人补电话/线下功能,无任何护栏。
- 平台内联系渠道:现有通知仅是单向 notification(`notificationService.send/sendAsync`),无红娘↔用户平台内会话通道;B1 的"平台内服务申请/回应"仅覆盖转介确认流。
**功能缺口**:推荐弹窗要求红娘手输 targetUserId(前端 L62"输入被推荐对象的用户ID")——红娘既无全站检索权(正确),也没有任何"从用户已提供的择偶需求/公开池中获取候选"的受控途径,主流程实际不可用【部分实现】。
### 2.5 红娘代替用户操作(DEC-004)
**未发现代替用户决策的接口**【已实现】:
- 红娘推荐只建 `MatchmakerRecommendation(status=pending_view)`,明确注释"这不是替用户 Like!""红娘点创建 Match → 不允许!"(`MatchmakerService` L97-105),并且有归属校验(L117-121)。
- Like/Pass 仅由用户本人端点触发:`POST /matchmaker/recommendations/{id}/accept|pass`(`MatchmakerController` L81-96)→ `acceptRecommendation` 用 `findByIdAndUser(recommendationId, userId)` 归属校验(`MatchmakerService` L213-222),Pass 同理(L246-255)。
- 红娘无法以用户身份调用:用户端点走 `UserPrincipal`,运营端走 `OperatorPrincipal`(两套 principal 体系,`shared/security/principal/`),且测试锁定"红娘无法用别人的用户身份接受推荐"(`MatchmakerReferralPhase13Test` L158-171)。
- 转介确认(活动/预约)须用户本人 `confirmReferral`,校验 `userId.equals(appt.getUserId())`(`ReferralService` L145-146、L166-167),非本人确认被拒并有测试(`MatchmakerReferralPhase13Test` L255-269)。
- 无修改用户择偶条件、同意公开资料的红娘入口(matchmaker 角色无 user 相关权限)。
- `matchmaker.assign` 的实际用途 = 指派工单给红娘,**不是**"代替分配对象做操作";其风险在于指派时 `organizationId` 参数恒传 null(Controller L46 传 `null`),且前端弹窗是硬编码假数据(见 2.8)。
⚠️ 唯一灰色点:转介会**代替用户创建** `activity_registration(status=pending)` / `service_appointment(status=submitted)` 记录(`ReferralService` L74、L114),但带 `source=matchmaker_referral` 标记且必须用户本人确认后生效(`needsUserConfirmation=true`),语义上是"代填草稿、用户确认",与 DEC-004 底线可兼容,但建议在 ZC-MM-004 复核其与"代填关键个人信息"边界的表述。
### 2.6 审计日志覆盖(DEC-006)
**基础设施**【已实现】:
- `audit_logs` 表 + `AuditLog` 实体(operatorId/organizationId/action/resourceType/resourceId/beforeData/afterData/ip/userAgent)。
- `sensitive_access_logs` + `SensitiveAccessLog`(operatorId/userId/dataType/reason/accessTarget/accessResult/ip)。
- `AuthorizationAuditListener`(L25-49):`@PreAuthorize` 拒绝自动写 `AUTHORIZATION_DENIED`(含 IP/UA)→ **@PreAuthorize 层的越权拒绝已覆盖**。
- `DataScopeGuard.deny`(L21-36):写 `DATA_SCOPE_DENIED` 后抛 403。
**matchmaker 模块实际覆盖**【部分实现,缺口明确】:
| 操作 | 审计现状 |
|------|---------|
| 分配 assign | ✅ `MATCHMAKER_ASSIGN`(L102),但 beforeData/afterData 恒 null(saveAuditLog L395-403),**不记录分配人以外信息(谁指派=principal.id 即 assignOperatorId=被指派红娘,见 Controller L44-46 的参数命名混乱:body.operatorId 是"被分配红娘",操作者是 principal,审计里存的是前者)** |
| 认领 claim | ❌ 无审计(L109-122 无 saveAuditLog) |
| 推荐 recommend | ✅ `MATCHMAKER_RECOMMEND`(L174) |
| 跟进 followup | ✅ `MATCHMAKER_FOLLOWUP`(L201) |
| 完成 complete | ✅ `MATCHMAKER_COMPLETE` 但 operatorId 传 null(L222)——审计丢失操作者 |
| 取消 cancel | ❌ 死代码且无审计 |
| 查看敏感详情 getServiceDetail | ❌ 不写 SensitiveAccessLog、不调 recordSensitiveAccess(对照:ReviewService L470-477 访问本地资格佐证时写 SensitiveAccessLog;AuditLogService.recordSensitiveAccess L74-88 已有现成方法) |
| 模块内归属拒绝(recommend/followup 的 IllegalStateException、ReferralService 的 IllegalStateException) | ❌ 不走 DataScopeGuard,不产生 DATA_SCOPE_DENIED;仅裸抛异常 → **服务层越权拒绝不落审计**,违反 MM-AUDIT-002 |
| 缺失字段 | 审计无 purpose_code/service_assignment_id/actor_role_snapshot/request_trace_id(DEC-006 第八节字段要求),AuditLog 实体不含这些列 |
### 2.7 可复用模块
| 组件 | 位置 | 与 A1/B1 的关系 |
|------|------|----------------|
| **CounselingService 的 INV-012 判定链路** | `counseling/service/CounselingService.java` L181-200(assign 用条件更新 assignIfPending 防并发)、assertAssigned 模式(L205/232/242 各写接口前置)、`GET /counseling/services/operator/assigned`(Controller L141-145 → assignedTo) | **与 A1 最接近**:"只有被分配人可见/可操作" 的完整范式,含权限矩阵注释(L38-48 定义 6 种角色的列表/脱敏/全文视图)。红娘"本人有效分配对象"可直接套此模式 |
| **DataScopeGuard** | `shared/security/DataScopeGuard.java` L21-36 | 拒绝+审计一体,activity/counseling/service_appointment 已用;红娘模块的归属拒绝应统一接入 |
| **AuthorizationAuditListener** | `shared/security/AuthorizationAuditListener.java` | @PreAuthorize 拒绝自动审计,MM-AUDIT-002 的 403 层已覆盖 |
| **AuditLogService.recordSensitiveAccess + SensitiveAccessLog** | `audit/service/AuditLogService.java` L74-88;使用范例 `review/service/ReviewService.java` L470-477 | 受限字段/资料查看审计现成方法(matchmaker 详情接口应调用) |
| **MatchmakerServiceMapper.findActiveByOperatorAndUser** | `matchmaker/mapper/MatchmakerServiceMapper.java` L48-54 | 现成的"本人名下+该用户+有效状态"判定 SQL,是未来 ServiceAssignmentPolicy 判定的雏形(状态集含 submitted 待收紧) |
| **OperatorAuthService 权限装载** | `auth/service/OperatorAuthService.java` L98-108(权限集合仅登录时装载进 JWT claims) | 权限事实来源;同时是权限回收的**风险点**(见 P1-7) |
| 用户端归属校验范式 | `MatchmakerRecommendationMapper.findByIdAndUser` + `MatchmakerService.acceptRecommendation` L213-222 | "资源归属=当前主体"校验样板 |
### 2.8 前端现状
- `MatchmakerManagementView.vue`:服务请求队列 tab 对所有 `matchmaker.view` 用户展示全库 pending(userId+需求说明,L7-25);**分配弹窗红娘下拉是硬编码假数据** `operator_wang/operator_chen`(L47-50)——分配功能前端不可真实使用;推荐弹窗需手输 targetUserId(L62)。
- 路由/菜单:`router/index.ts` L40-41、`MainLayout.vue` L177-178 权限码已与后端对齐(matchmaker.view / matchmaker.service,V052 注释所述错配已修复)。
- `ReferralManagementView.vue` L35 已改走 `/referrals/operator/history`(契约缺口已修)。
### 2.9 治理文档现状
- `docs/governance/decision-register.md`:grep `DEC-MATCHMAKER|matchmaker` 无结果【缺失】。
- `docs/governance/open-questions.md`:无 matchmaker/电话/线下联系条目【缺失】。
- `docs/PENDING_DECISIONS.md`:无 A1/B1/matchmaker 条目【缺失】。
- `docs/audits/` 目录不存在(本报告即首次建立)。
### 2.10 现有测试
| 测试 | 覆盖 | 缺口 |
|------|------|------|
| `MatchmakerReferralPhase13Test` | MM-ACTION 方向较完整:推荐不产生 Like/Match(L132-141)、只有用户能 Like(L143)、红娘不能代用户接受(L158-171)、转介归属校验(L288-296)、用户本人确认(L225-269) | 不含数据范围/联系方式/审计断言 |
| `MatchmakerServiceTest` | 仅校验常量与 DB CHECK 白名单一致(L94-177),**纯守卫测试,无权限断言** | — |
| `PermissionMatrixTest` | 静态 Map 自断言(L25-224 + L229 起),**不连接 DB/迁移文件**,且期望值(matchmaker 有 user.view=true,L119)与 V052 实际授权(无 user.view)漂移 | 矩阵不校验真实授权,是"看起来有保护"的假阳性 |
| `OperatorEndpointPermissionCoverageTest` | 机械守卫:确保运营端 Controller 方法有 @PreAuthorize(L10、L43) | 只查注解存在性,不校验权限码正确性 |
| SecurityTests / V11InvariantsTest | 通用授权拒绝、INV 守卫 | 无 MM-AUTH/CONTACT/PRIVACY/AUDIT/LIFECYCLE 专属场景 |
**测试基线覆盖判定**:MM-ACTION ≈ 部分覆盖;MM-AUTH/MM-CONTACT/MM-PRIVACY/MM-AUDIT/MM-LIFECYCLE ≈ 全部缺失。
---
## 3. 可复用模块清单
(按建议采用优先级)
1. **CounselingService INV-012 范式**(`CounselingService` L181-200 条件更新分配、assertAssigned 写接口前置、Controller L141-145 assigned 列表)——A1"本人有效分配对象"的直接模板。
2. **DataScopeGuard**(`shared/security/DataScopeGuard.java`)——归属拒绝统一走 deny()=审计+403。
3. **AuthorizationAuditListener**——已有,无需新建。
4. **AuditLogService.recordSensitiveAccess + SensitiveAccessLog**(ReviewService L470-477 为使用范例)——受限字段查看审计。
5. **MatchmakerServiceMapper.findActiveByOperatorAndUser**——分配有效性判定 SQL 雏形。
6. **OperatorAuthService/JwtAuthFilter 权限装载链**——若做权限回收需改造此链路(JWT claims 快照问题)。
7. 无 MatchmakerPolicy/PrivacyPolicy/ContactAuthorizationPolicy 任何现存实现——需新建。
---
## 4. 差距与风险清单(按优先级)
### P0(违反 LOCKED 决策,阻断 DoD)
| # | 差距 | 违反 | 影响 | 证据 |
|---|------|------|------|------|
| P0-1 | 服务对象分配未建模:无 matchmaker_assignments/独立分配实体,分配=工单指派且无 assigned_by/理由/有效期/结束语义/分配历史 | DEC-002 规则6/7、5.1 概念模型 | A1 无法落地;分配不可追溯、不可回收、不可过期 | V009 L5-20;全仓 grep 无 assignment 实体 |
| P0-2 | 普通红娘认领入口开放且无归属校验:claim 任意 assigned 单;followup/complete 对任意 serviceId 可写/可完结 | DEC-002 规则1/2;DEC-005;MM-AUTH-004 | 任何红娘可干预他人服务对象、写入跟进内容、变更服务状态 | MatchmakerOperatorController L49-56、L99-118;MatchmakerServiceService L109-122、L186-203、L208-224 |
| P0-3 | 未分配申请队列+全局统计对全体红娘可见(含申请人 userId 与诉求描述) | DEC-002 规则1;DEC-005;MM-AUTH-002/PRIVACY-001 精神 | 普通红娘可浏览"待分配用户库"挑选对象,正面违反 A1 | MatchmakerOperatorController L32-36;MatchmakerServiceService L245-258、L260-276;Mapper findSubmitted L17-18 |
| P0-4 | 无红娘组长角色与团队管理范围概念,assign 权限无范围约束(谁能 assign 只有权限码、无组织/团队边界;organizationId 恒传 null) | DEC-002 规则4/5/8 | 分配权无法收敛到"平台授权人员或组长";跨团队数据范围无依据 | MatchmakerOperatorController L38-47(organizationId=null);全仓无 leader 角色 |
| P0-5 | 服务层归属/范围拒绝不产生审计(不走 DataScopeGuard),claim 无审计、complete 审计丢操作者、分配审计字段残缺 | DEC-006;MM-AUDIT-001/002 | 分配与越权链条不可追溯 | MatchmakerServiceService L102/L109-122/L222/L395-403;DataScopeGuard 使用方不含 matchmaker |
### P1(安全机制缺口,DoD 必补)
| # | 差距 | 违反 | 影响 | 证据 |
|---|------|------|------|------|
| P1-6 | 非负责人可读任意服务单脱敏详情+他人推荐列表(targetUserId、推荐理由),泄露存在性与他人服务内容 | DEC-005(响应不得泄露目标用户是否存在);MM-PRIVACY-001 | 横向探测面 | MatchmakerServiceService L301-351(L317-327 对非 owner 装配 recommendations) |
| P1-7 | 运营权限是登录时 JWT claims 快照(JwtAuthFilter L90-99 直接取 claims),停用/调岗/权限回收后旧 token 有效期内仍放行 | DEC-005/006(权限及时回收);MM-LIFECYCLE-001 | 权限回收延迟=token 剩余有效期 | OperatorAuthService L98-108;JwtAuthFilter L90-99 |
| P1-8 | 受限资料查看无 SensitiveAccessLog(服务详情 userProfile 属受限资料但零记录) | DEC-006;MM-AUDIT 方向 | 敏感读取不可溯源 | MatchmakerServiceService L329-348;对照 ReviewService L470-477 |
| P1-9 | 无统一 Policy 层(MatchmakerPolicy/DataScopePolicy/PrivacyPolicy),范围校验散落且口径不一 | DEC-005 判定链要求 | 后续每加一个接口都可能漏校验(现状已证明) | §2.3 表格 |
| P1-10 | B1 联系授权机制完全缺失(无表无策略无审批流),当前安全是"功能不存在"而非"有护栏" | DEC-003 规则7 | 一旦有人新增电话/线下功能即裸奔;DoD "无授权电话/线下联系被拒绝" 无承载 | grep ContactAuthorization 无结果 |
| P1-11 | 测试基线缺口:MM-AUTH/CONTACT/PRIVACY/AUDIT/LIFECYCLE 无场景测试;PermissionMatrixTest 与真实授权漂移且自引用 | 决策文档第十节 | 回归无保护网 | §2.10 |
### P2(质量/体验/治理)
| # | 差距 | 影响 | 证据 |
|---|------|------|------|
| P2-12 | 分配弹窗硬编码假红娘(operator_wang/chen),分配功能前端不可用 | 主流程断裂 | MatchmakerManagementView.vue L47-50 |
| P2-13 | 推荐对象需手输 userId,无受控候选来源,推荐主流程实际不可用 | 业务不可用(但不可用方向是安全的) | 同上 L62 |
| P2-14 | dashboard inProgress 统计用 'processing' 与 DB 白名单 'in_service' 不符,恒为 0 | 数据失真 | MatchmakerServiceMapper L28-30 vs V009 L30-32 |
| P2-15 | cancelService 死代码;matchmaker.assign 的 organizationId 参数恒 null | 代码卫生 | MatchmakerServiceService L229-237;Controller L46 |
| P2-16 | 治理文档无 DEC-MATCHMAKER/A1/B1 条目;docs/audits 目录此前不存在 | 治理不同步(ZC-MM-007 范围) | §2.9 |
| P2-17 | PermissionMatrixTest 期望 platform_admin/reviewer 等角色矩阵,但迁移未定义这些角色的 matchmaker 授权,实际库状态无法确定 | 授权事实不清 | V052 仅授 role 005;V044 仅补 super_admin |
---
## 5. 仍需后续确认的实现细节(对应决策文档第十一节,仓库现状)
| 待确认项 | 仓库现状 |
|---------|---------|
| 电话/线下联系例外批准岗位 | 无任何授权机制/审批流/岗位模型(grep 无结果);需产品确认后从零建 |
| 例外授权是否需用户单独同意及记录方式 | 无载体;`activity_registration`/`service_appointment` 已有 `user_confirmed_at`+`source=matchmaker_referral` 的用户确认范式(ReferralService L151-152)可作参考 |
| 临时授权有效期限 | 无 expires_at/有效期字段于任何红娘相关表(matchmaker_services 也没有);`matchmaker_recommendations` 无过期机制 |
| 审计日志/服务摘要保留周期 | 迁移中未见 audit_logs 的保留/清理策略;account 模块有 DataRetentionService(`account/service/DataRetentionService.java`)可参考但未覆盖审计日志【保留周期无法确定】 |
| 红娘组长可分配的组织/团队边界 | `organizations` 表与 `matchmaker_services.organization_id` 存在(V009 L11),operator 与组织的关联关系存在于 operator 体系,但"团队/红娘组"概念不存在,边界配置无从谈起 |
| platform_admin/organization_admin 的 matchmaker 权限实际授权 | 迁移不可证【无法确定,需查演示库】 |
| 转介代建 pending 报名/预约与"代填关键信息"边界 | 见 §2.5 灰色点,建议 ZC-MM-004 复核时与产品确认表述 |
---
## 6. 对 ZC-MM-002~007 实施顺序的建议(仅顺序与依赖)
1. **ZC-MM-002(A1 分配)先行且作为一切的地基**:必须先落 `matchmaker_assignments` 独立分配关系(含 assigned_by/reason/status/expires/ended)+ 红娘组长角色 + 分配状态机。理由:P0-1~P0-4 全部依赖此建模;后续所有数据范围判定都以"有效分配"为事实源。
2. **ZC-MM-005(统一数据范围与字段隐私)紧跟 002**:把 claim/followup/complete/pending/dashboard/详情统一接入范围判定(复用 CounselingService INV-012 + DataScopeGuard)。依赖 002 的分配事实源;不先做会继续在旧工单模型上打补丁。
3. **ZC-MM-003(B1 联系规则)与 004(禁止代操作复核)可并行**:003 当前主要是"补护栏+固化现状"(关闭暴露面+建立联系授权的拒绝默认),004 主要是复核与测试锁定(现状已基本合规)。两者都不依赖 002 的表结构,但 003 的"平台内服务通知渠道"若要做成会话类功能,建议在 005 稳定后做。
4. **ZC-MM-006(审计与权限回收)在 002/005 之后收口**:分配/转交审计、SensitiveAccessLog 接入、模块内拒绝走 DataScopeGuard 都依赖 002 的新实体与 005 的判定点;JWT 权限回收(P1-7)涉及 auth 链路改造,放在业务语义稳定之后风险最小。
5. **ZC-MM-007(治理文档)最后**:登记 DEC-MATCHMAKER-001~006 与 A1/B1、open-questions(第十一节五项)、本报告归档于 docs/audits。
6. **贯穿性建议**:每阶段同步补 MM-AUTH/CONTACT/ACTION/PRIVACY/AUDIT/LIFECYCLE 测试基线(当前几乎为零),并把 PermissionMatrixTest 从静态自引用改为对迁移/真实授权的校验——否则 P1-11 的假阳性会掩盖回归。
---
*(审计完毕。本报告由架构师产出、主理人汇编落盘。后续实施需 Owner 授权后按 ZC-MM-002~007 分阶段进行。)*