forked from hangshuo652/aurak
a7e7c85ff6
缺陷修复: - 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>
249 lines
8.5 KiB
TypeScript
249 lines
8.5 KiB
TypeScript
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 admin(isAdmin=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;
|
||
}
|
||
}
|