/
tm-project
/
rbac-react-system
Обзор
Документация
Войти
/
tm-project
/
rbac-react-system
Код
Запросы
0
Пакеты
0
Релизы
1
CI/CD
Аналитика
Безопасность
gitverse
src/application/use-cases/CheckPermissionUseCase.ts
219 строк
10 KB
Artem
chore: release v1.0.4
30 ноя 2025, 22:51
30 ноя 2025, 22:51
f6f509c
Код
Авторство
О чём код?
/** * rbac-react-system v1.0.4 * Copyright (c) 2025 TM Project * Licensed under the MIT License */ import { IPermissionRepository, IRoleRepository, IUserRepository } from '../../domain/interfaces' import { Condition, Permission, User } from '../../domain/entities' /** * Use case для проверки наличия у пользователя определённого разрешения. * Реализует основной алгоритм проверки прав доступа с учётом ролей, прямых разрешений и контекстных условий. */ export class CheckPermissionUseCase { /** * Создаёт экземпляр use case для проверки разрешений. * * @param {IUserRepository} userRepository - Репозиторий для доступа к данным пользователей. * @param {IPermissionRepository} permissionRepository - Репозиторий для доступа к данным разрешений. * @param {IRoleRepository} roleRepository - Репозиторий для доступа к данным ролей. */ constructor( private readonly userRepository: IUserRepository, private readonly permissionRepository: IPermissionRepository, private readonly roleRepository: IRoleRepository ) {} /** * Выполняет проверку, имеет ли пользователь право на выполнение указанного действия над ресурсом. * Проверка включает: * - прямые разрешения пользователя, * - разрешения, полученные через роли и их иерархию, * - оценку условий доступа (если они заданы). * * @param {string} userId - Идентификатор пользователя, для которого проверяется доступ. * @param {string} resource - Ресурс, к которому запрашивается доступ (например, 'users', 'posts'). * @param {string} action - Действие, которое необходимо выполнить (например, 'read', 'write', 'delete'). * @returns {Promise<boolean>} Промис, разрешающийся в `true`, если доступ разрешён, иначе — `false`. */ async execute(userId: string, resource: string, action: string): Promise<boolean> { try { // Шаг 1: Загрузка данных пользователя const user = await this.userRepository.findUserById(userId) if (!user) { return false } // Шаг 2: Сбор всех разрешений пользователя (прямые + по ролям) const allPermissions = await this.collectAllPermissions(user) // Шаг 3: Поиск разрешения, соответствующего запрашиваемым ресурсу и действию const matchingPermission = allPermissions.find( p => p.resource === resource && p.action === action ) if (!matchingPermission) { return false } // Шаг 4: Проверка условий, если они определены if (matchingPermission.conditions && matchingPermission.conditions.length > 0) { return this.evaluateConditions(matchingPermission.conditions, user) } return true } catch (error) { // При возникновении ошибки доступ запрещается (fail-safe) console.error('Ошибка при проверке прав доступа:', error) return false } } /** * Собирает все разрешения пользователя: как прямые, так и полученные через роли (включая наследуемые). * * @param {User} user - Пользователь, для которого собираются разрешения. * @returns {Promise<Permission[]>} Промис, разрешающийся массивом всех разрешений пользователя. */ private async collectAllPermissions(user: User): Promise<Permission[]> { const permissions: Permission[] = [] // Добавляем прямые разрешения пользователя const userPermissions = await this.permissionRepository.getUserPermissions(user.id) permissions.push(...userPermissions) // Добавляем разрешения, полученные по ролям const rolePermissions = await this.getRolePermissions(user.roles) permissions.push(...rolePermissions) // Удаляем дубликаты по комбинации "ресурс:действие" return this.deduplicatePermissions(permissions) } /** * Рекурсивно получает все разрешения для указанных ролей, включая разрешения от наследуемых ролей. * * @param {string[]} roleIds - Массив идентификаторов ролей пользователя. * @returns {Promise<Permission[]>} Промис, разрешающийся массивом разрешений всех ролей и их иерархии. */ private async getRolePermissions(roleIds: string[]): Promise<Permission[]> { const permissions: Permission[] = [] const processedRoles = new Set<string>() // Для предотвращения циклического обхода const processRole = async (roleId: string): Promise<void> => { if (processedRoles.has(roleId)) { return // Роль уже обработана — избегаем зацикливания } processedRoles.add(roleId) const role = await this.roleRepository.findRoleById(roleId) if (!role) { return } // Добавляем разрешения текущей роли permissions.push(...role.permissions) // Рекурсивно обрабатываем все наследуемые роли if (role.inheritedRoles && role.inheritedRoles.length > 0) { for (const inheritedRoleId of role.inheritedRoles) { await processRole(inheritedRoleId) } } } // Обрабатываем каждую роль пользователя for (const roleId of roleIds) { await processRole(roleId) } return permissions } /** * Удаляет дубликаты разрешений на основе комбинации "ресурс:действие". * * @param {Permission[]} permissions - Массив разрешений, в котором нужно убрать дубликаты. * @returns {Permission[]} Новый массив разрешений без дубликатов. */ private deduplicatePermissions(permissions: Permission[]): Permission[] { const seen = new Map<string, Permission>() for (const permission of permissions) { const key = `${permission.resource}:${permission.action}` if (!seen.has(key)) { seen.set(key, permission) } } return Array.from(seen.values()) } /** * Оценивает все условия разрешения. Доступ предоставляется только если все условия возвращают `true`. * * @param {Condition[]} conditions - Массив условий, которые необходимо проверить. * @param {User} user - Пользователь, чьи атрибуты используются при оценке условий. * @returns {boolean} `true`, если все условия выполнены, иначе — `false`. */ private evaluateConditions(conditions: Condition[], user: User): boolean { return conditions.every(condition => this.evaluateCondition(condition, user)) } /** * Оценивает одиночное условие на основе атрибутов пользователя. * * @param {Condition} condition - Условие, которое нужно проверить. * @param {User} user - Пользователь, чьи атрибуты используются для сравнения. * @returns {boolean} Результат проверки условия. */ private evaluateCondition(condition: Condition, user: User): boolean { const userValue = user.attributes[condition.field] switch (condition.operator) { case 'equals': return userValue === condition.value case 'notEquals': return userValue !== condition.value case 'contains': if (typeof userValue === 'string' && typeof condition.value === 'string') { return userValue.includes(condition.value) } if (Array.isArray(userValue)) { return userValue.includes(condition.value) } return false case 'greaterThan': if (typeof userValue === 'number' && typeof condition.value === 'number') { return userValue > condition.value } return false case 'lessThan': if (typeof userValue === 'number' && typeof condition.value === 'number') { return userValue < condition.value } return false case 'in': if (Array.isArray(condition.value)) { return (condition.value as Array<string | number>).includes(userValue as string | number) } return false case 'notIn': if (Array.isArray(condition.value)) { return !(condition.value as Array<string | number>).includes(userValue as string | number) } return false default: return false } } }