Files
aurak/server/src/auth/permission/permission.service.ts
T
Developer a7e7c85ff6 fix: 系统角色权限保护 + 全角色全场景 E2E 测试(94项)
缺陷修复:
- PermissionService.setRolePermissions 增加 isSystem 检查
  系统角色的权限不可被修改(之前可被任意改写)

测试覆盖(94项全部通过):
- PHASE A: 身份认证(登录/错误密码/无效token/空凭据 8项)
- PHASE B: 三层角色权限边界(26/21/5 权限一致性 3项)
- PHASE C: 创建用户异常(重复/短密码/空字段/特殊字符 7项)
- PHASE D: 编辑&角色变更(改名/升降级/非法值/并发/跨角色 12项)
- PHASE E: 删除异常(删自己/admin/不存在/USER删/TA删 12项)
- PHASE F: 权限系统(角色CRUD/权限改/权限一致性/元数据 25项)
- PHASE G: 模块可达性(2项,非致命)
- PHASE H: 前端UI(admin/ta_admin/user1 三角色 22项)
- PHASE I: 边界缺陷(跨租户隔离/超长名 2项)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-09 09:41:04 +08:00

249 lines
8.5 KiB
TypeScript
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.
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Role } from './role.entity';
import { RolePermission } from './role-permission.entity';
import { ALL_PERMISSIONS, PERMISSION_META, PermissionKey, PermissionMeta } from './permission.constants';
import { UserRole } from '../../user/user-role.enum';
import { TenantMember } from '../../tenant/tenant-member.entity';
import { User } from '../../user/user.entity';
import { ConfigService } from '@nestjs/config';
/**
* 角色预设——系统内置角色默认挂载的权限
*/
const ROLE_DEFAULT_PERMISSIONS: Record<string, PermissionKey[]> = {
[UserRole.SUPER_ADMIN]: [...ALL_PERMISSIONS],
[UserRole.TENANT_ADMIN]: [
// 用户管理(不含删除和角色管理——安全考虑)
'user:view', 'user:create', 'user:edit', 'user:password',
// 租户管理(只能查看和编辑自己的)
'tenant:view', 'tenant:edit', 'tenant:members',
// 知识库
'kb:view', 'kb:create', 'kb:edit', 'kb:delete', 'kb:publish',
// 考核评估
'assess:view', 'assess:manage', 'assess:template', 'assess:bank',
// 模型配置
'model:view', 'model:config',
// 插件
'plugin:view', 'plugin:manage',
// 设置
'settings:view',
],
[UserRole.USER]: [
'kb:view', 'kb:create', 'kb:edit',
'assess:view',
'plugin:view',
],
};
@Injectable()
export class PermissionService implements OnModuleInit {
private readonly logger = new Logger(PermissionService.name);
constructor(
@InjectRepository(Role)
private readonly roleRepository: Repository<Role>,
@InjectRepository(RolePermission)
private readonly rolePermissionRepository: Repository<RolePermission>,
@InjectRepository(TenantMember)
private readonly tenantMemberRepository: Repository<TenantMember>,
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly configService: ConfigService,
) {}
/**
* 启动时自动种子化系统角色和权限
*/
async onModuleInit() {
const isTest = this.configService.get<string>('NODE_ENV') === 'test';
if (isTest) return;
try {
const existing = await this.roleRepository.count({ where: { isSystem: true } });
if (existing > 0) {
this.logger.log(`[Permission Seed] ${existing} 个系统角色已存在,跳过`);
return;
}
await this.seedSystemRoles();
this.logger.log('[Permission Seed] ✅ 系统角色和权限已初始化');
} catch (err) {
this.logger.error('[Permission Seed] ❌ 初始化失败:', err);
}
}
private async seedSystemRoles() {
const roles = [
{ name: UserRole.SUPER_ADMIN, baseRole: UserRole.SUPER_ADMIN },
{ name: UserRole.TENANT_ADMIN, baseRole: UserRole.TENANT_ADMIN },
{ name: UserRole.USER, baseRole: UserRole.USER },
];
for (const r of roles) {
const role = await this.roleRepository.save({
name: r.name,
description: this.getRoleDescription(r.name as UserRole),
isSystem: true,
baseRole: r.baseRole,
tenantId: null,
});
const perms = ROLE_DEFAULT_PERMISSIONS[r.name] || [];
if (perms.length > 0) {
await this.rolePermissionRepository.save(
perms.map(key => ({ roleId: role.id, permissionKey: key })),
);
}
this.logger.log(` - ${r.name}: ${perms.length} 项权限`);
}
}
private getRoleDescription(role: UserRole): string {
switch (role) {
case UserRole.SUPER_ADMIN: return '全局超级管理员——拥有系统全部权限';
case UserRole.TENANT_ADMIN: return '租户管理员——管理本租户内的用户和资源';
case UserRole.USER: return '普通用户——使用系统功能';
}
}
// ──────────── 角色 CRUD ────────────
async findAllRoles(tenantId?: string): Promise<Role[]> {
const where: any[] = [{ isSystem: true, tenantId: null }];
if (tenantId) {
where.push({ tenantId, isSystem: false });
}
return this.roleRepository.find({ where, order: { isSystem: 'DESC', name: 'ASC' } });
}
async findRoleById(id: string): Promise<Role | null> {
return this.roleRepository.findOne({ where: { id } });
}
async createRole(name: string, description: string, tenantId: string): Promise<Role> {
const existing = await this.roleRepository.findOne({ where: { name } });
if (existing) throw new Error(`角色名 "${name}" 已存在`);
return this.roleRepository.save({
name,
description,
isSystem: false,
baseRole: null,
tenantId,
});
}
async updateRole(id: string, data: { name?: string; description?: string }): Promise<Role> {
const role = await this.roleRepository.findOne({ where: { id } });
if (!role) throw new Error('角色不存在');
if (role.isSystem) throw new Error('系统角色不可编辑');
if (data.name) role.name = data.name;
if (data.description !== undefined) role.description = data.description;
return this.roleRepository.save(role);
}
async deleteRole(id: string): Promise<void> {
const role = await this.roleRepository.findOne({ where: { id } });
if (!role) throw new Error('角色不存在');
if (role.isSystem) throw new Error('系统角色不可删除');
await this.roleRepository.remove(role);
}
// ──────────── 权限管理 ────────────
async getRolePermissions(roleId: string): Promise<string[]> {
const rps = await this.rolePermissionRepository.find({
where: { roleId },
});
return rps.map(rp => rp.permissionKey);
}
async setRolePermissions(roleId: string, permissionKeys: string[]): Promise<void> {
const role = await this.roleRepository.findOne({ where: { id: roleId } });
if (!role) throw new Error('角色不存在');
if (role.isSystem) throw new Error('系统角色的权限不可修改');
// 验证权限键是否有效
const valid = ALL_PERMISSIONS;
const invalid = permissionKeys.filter(k => !valid.includes(k as any));
if (invalid.length > 0) throw new Error(`无效的权限: ${invalid.join(', ')}`);
// 替换角色的所有权限
await this.rolePermissionRepository.delete({ roleId });
if (permissionKeys.length > 0) {
await this.rolePermissionRepository.save(
permissionKeys.map(key => ({ roleId, permissionKey: key })),
);
}
}
// ──────────── 用户权限解析 ────────────
/**
* 获取用户在指定租户下的最终权限集
*
* 解析链路:
* 1. 如果是 global adminisAdmin=true),直接返回所有权限
* 2. 通过 TenantMember.role → 对应 role → role_permissions
*/
async getUserPermissions(userId: string, tenantId: string): Promise<Set<string>> {
// 检查全局管理员(遗留 isAdmin 字段)
const user = await this.userRepository.findOne({
where: { id: userId },
select: ['id', 'isAdmin'],
});
if (user?.isAdmin) {
const superAdminRole = await this.roleRepository.findOne({
where: { baseRole: UserRole.SUPER_ADMIN, isSystem: true },
});
if (superAdminRole) {
const perms = await this.getRolePermissions(superAdminRole.id);
return new Set(perms);
}
}
// 通过租户成员角色
const membership = await this.tenantMemberRepository.findOne({
where: { userId, tenantId },
});
if (!membership) return new Set();
const role = await this.roleRepository.findOne({
where: { baseRole: membership.role, isSystem: true },
});
if (!role) return new Set();
const perms = await this.getRolePermissions(role.id);
return new Set(perms);
}
/**
* 检查用户是否有指定权限
*/
async checkPermission(userId: string, tenantId: string, permissionKey: string): Promise<boolean> {
// 先尝试从请求级缓存获取(由 PermissionsGuard 设置)
const perms = await this.getUserPermissions(userId, tenantId);
return perms.has(permissionKey);
}
// ──────────── 元数据 ────────────
getAllPermissionMeta(): PermissionMeta[] {
return PERMISSION_META;
}
getPermissionsByCategory(): Record<string, PermissionMeta[]> {
const grouped: Record<string, PermissionMeta[]> = {};
for (const meta of PERMISSION_META) {
if (!grouped[meta.category]) grouped[meta.category] = [];
grouped[meta.category].push(meta);
}
return grouped;
}
}