463 lines
14 KiB
PHP
463 lines
14 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Models\OrganizationUserModel;
|
||
use CodeIgniter\Database\Exceptions\DataException;
|
||
|
||
/**
|
||
* AccessService - Сервис проверки прав доступа (RBAC)
|
||
*
|
||
* Управляет правами внутренних пользователей организации.
|
||
* Для внешних пользователей (публичные ссылки) используется ExternalAccessService.
|
||
*/
|
||
class AccessService
|
||
{
|
||
private ?array $currentMembership = null;
|
||
private OrganizationUserModel $orgUserModel;
|
||
|
||
/**
|
||
* Роли и их уровни (для быстрого сравнения)
|
||
*/
|
||
public const ROLE_OWNER = 'owner';
|
||
public const ROLE_ADMIN = 'admin';
|
||
public const ROLE_MANAGER = 'manager';
|
||
public const ROLE_GUEST = 'guest';
|
||
|
||
/**
|
||
* Иерархия ролей (больше = выше привилегии)
|
||
*/
|
||
public const ROLE_HIERARCHY = [
|
||
self::ROLE_OWNER => 100,
|
||
self::ROLE_ADMIN => 75,
|
||
self::ROLE_MANAGER => 50,
|
||
self::ROLE_GUEST => 25,
|
||
];
|
||
|
||
/**
|
||
* Права на действия
|
||
*/
|
||
public const PERMISSION_VIEW = 'view';
|
||
public const PERMISSION_CREATE = 'create';
|
||
public const PERMISSION_EDIT = 'edit';
|
||
public const PERMISSION_DELETE = 'delete';
|
||
public const PERMISSION_DELETE_ANY = 'delete_any';
|
||
public const PERMISSION_MANAGE_USERS = 'manage_users';
|
||
public const PERMISSION_MANAGE_MODULES = 'manage_modules';
|
||
public const PERMISSION_VIEW_FINANCE = 'view_finance';
|
||
public const PERMISSION_DELETE_ORG = 'delete_org';
|
||
public const PERMISSION_TRANSFER_OWNER = 'transfer_owner';
|
||
|
||
/**
|
||
* Матрица прав по ролям
|
||
* Формат: [роль => [ресурс => [действия]]]
|
||
*/
|
||
private const ROLE_PERMISSIONS = [
|
||
self::ROLE_OWNER => [
|
||
'*' => ['*'], // Полный доступ ко всему
|
||
],
|
||
self::ROLE_ADMIN => [
|
||
'clients' => ['view', 'create', 'edit', 'delete'],
|
||
'deals' => ['view', 'create', 'edit', 'delete'],
|
||
'bookings' => ['view', 'create', 'edit', 'delete'],
|
||
'projects' => ['view', 'create', 'edit', 'delete'],
|
||
'tasks' => ['view', 'create', 'edit', 'delete'],
|
||
'users' => [self::PERMISSION_VIEW, self::PERMISSION_CREATE, self::PERMISSION_EDIT, self::PERMISSION_DELETE],
|
||
self::PERMISSION_MANAGE_MODULES => [self::PERMISSION_VIEW, self::PERMISSION_EDIT],
|
||
self::PERMISSION_VIEW_FINANCE => ['*'],
|
||
],
|
||
self::ROLE_MANAGER => [
|
||
'clients' => ['view', 'create', 'edit', 'delete'],
|
||
'deals' => ['view', 'create', 'edit', 'delete'],
|
||
'bookings' => ['view', 'create', 'edit', 'delete'],
|
||
'projects' => ['view', 'create', 'edit', 'delete'],
|
||
'tasks' => ['view', 'create', 'edit', 'delete'],
|
||
'users' => [self::PERMISSION_VIEW], // Только просмотр коллег
|
||
],
|
||
self::ROLE_GUEST => [
|
||
'clients' => [self::PERMISSION_VIEW],
|
||
'deals' => [self::PERMISSION_VIEW],
|
||
'bookings' => [self::PERMISSION_VIEW],
|
||
'projects' => [self::PERMISSION_VIEW],
|
||
'tasks' => [self::PERMISSION_VIEW],
|
||
'users' => [self::PERMISSION_VIEW],
|
||
],
|
||
];
|
||
|
||
public function __construct()
|
||
{
|
||
$this->orgUserModel = new OrganizationUserModel();
|
||
}
|
||
|
||
/**
|
||
* Получение единственного экземпляра сервиса
|
||
*
|
||
* @return self
|
||
*/
|
||
public static function getInstance(): self
|
||
{
|
||
return new self();
|
||
}
|
||
|
||
/**
|
||
* Получение текущего membership пользователя
|
||
*
|
||
* @return array|null
|
||
*/
|
||
public function getCurrentMembership(): ?array
|
||
{
|
||
if ($this->currentMembership !== null) {
|
||
return $this->currentMembership;
|
||
}
|
||
|
||
$userId = session()->get('user_id');
|
||
$orgId = session()->get('active_org_id');
|
||
|
||
if (!$userId || !$orgId) {
|
||
return null;
|
||
}
|
||
|
||
$this->currentMembership = $this->orgUserModel
|
||
->where('user_id', $userId)
|
||
->where('organization_id', $orgId)
|
||
->first();
|
||
|
||
return $this->currentMembership;
|
||
}
|
||
|
||
/**
|
||
* Получение роли текущего пользователя
|
||
*
|
||
* @return string|null
|
||
*/
|
||
public function getCurrentRole(): ?string
|
||
{
|
||
$membership = $this->getCurrentMembership();
|
||
return $membership['role'] ?? null;
|
||
}
|
||
|
||
/**
|
||
* Проверка, авторизован ли пользователь в организации
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isAuthenticated(): bool
|
||
{
|
||
return $this->getCurrentMembership() !== null;
|
||
}
|
||
|
||
/**
|
||
* Проверка роли пользователя
|
||
*
|
||
* @param string|array $roles Роль или массив ролей для проверки
|
||
* @return bool
|
||
*/
|
||
public function isRole($roles): bool
|
||
{
|
||
$currentRole = $this->getCurrentRole();
|
||
if ($currentRole === null) {
|
||
return false;
|
||
}
|
||
|
||
$roles = (array) $roles;
|
||
return in_array($currentRole, $roles, true);
|
||
}
|
||
|
||
/**
|
||
* Проверка, является ли пользователь владельцем
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isOwner(): bool
|
||
{
|
||
return $this->getCurrentRole() === self::ROLE_OWNER;
|
||
}
|
||
|
||
/**
|
||
* Проверка, является ли пользователем администратором
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isAdmin(): bool
|
||
{
|
||
$role = $this->getCurrentRole();
|
||
return $role === self::ROLE_ADMIN || $role === self::ROLE_OWNER;
|
||
}
|
||
|
||
/**
|
||
* Проверка, является ли пользователем менеджером или выше
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function isManagerOrHigher(): bool
|
||
{
|
||
$role = $this->getCurrentRole();
|
||
return in_array($role, [self::ROLE_OWNER, self::ROLE_ADMIN, self::ROLE_MANAGER], true);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на действие
|
||
*
|
||
* @param string $action Действие (view, create, edit, delete, delete_any, manage_users, etc.)
|
||
* @param string $resource Ресурс (clients, deals, bookings, projects, tasks, users)
|
||
* @return bool
|
||
*/
|
||
public function can(string $action, string $resource): bool
|
||
{
|
||
$role = $this->getCurrentRole();
|
||
if ($role === null) {
|
||
return false;
|
||
}
|
||
|
||
$permissions = self::ROLE_PERMISSIONS[$role] ?? [];
|
||
|
||
// Проверка полного доступа (*)
|
||
if (isset($permissions['*']) && in_array('*', $permissions['*'], true)) {
|
||
return true;
|
||
}
|
||
|
||
// Проверка конкретного ресурса
|
||
if (!isset($permissions[$resource])) {
|
||
return false;
|
||
}
|
||
|
||
$resourcePermissions = $permissions[$resource];
|
||
|
||
// Проверка полного доступа к ресурсу
|
||
if (in_array('*', $resourcePermissions, true)) {
|
||
return true;
|
||
}
|
||
|
||
return in_array($action, $resourcePermissions, true);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на просмотр
|
||
*
|
||
* @param string $resource
|
||
* @return bool
|
||
*/
|
||
public function canView(string $resource): bool
|
||
{
|
||
return $this->can(self::PERMISSION_VIEW, $resource);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на создание
|
||
*
|
||
* @param string $resource
|
||
* @return bool
|
||
*/
|
||
public function canCreate(string $resource): bool
|
||
{
|
||
return $this->can(self::PERMISSION_CREATE, $resource);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на редактирование
|
||
*
|
||
* @param string $resource
|
||
* @return bool
|
||
*/
|
||
public function canEdit(string $resource): bool
|
||
{
|
||
return $this->can(self::PERMISSION_EDIT, $resource);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на удаление
|
||
*
|
||
* @param string $resource
|
||
* @param bool $any Удаление любой записи (не только своей)
|
||
* @return bool
|
||
*/
|
||
public function canDelete(string $resource, bool $any = false): bool
|
||
{
|
||
$action = $any ? self::PERMISSION_DELETE_ANY : self::PERMISSION_DELETE;
|
||
return $this->can($action, $resource);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на управление пользователями
|
||
*
|
||
* Управление пользователями недоступно для личных пространств.
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function canManageUsers(): bool
|
||
{
|
||
// Проверяем, что это бизнес-организация (не личное пространство)
|
||
$orgId = session()->get('active_org_id');
|
||
if (!$orgId) {
|
||
return false;
|
||
}
|
||
|
||
$orgModel = new \App\Models\OrganizationModel();
|
||
$organization = $orgModel->find($orgId);
|
||
|
||
// Личное пространство - управление пользователями недоступно
|
||
if ($organization && $organization['type'] === 'personal') {
|
||
return false;
|
||
}
|
||
|
||
return $this->can(self::PERMISSION_MANAGE_USERS, 'users');
|
||
}
|
||
|
||
/**
|
||
* Проверка права на управление модулями
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function canManageModules(): bool
|
||
{
|
||
return $this->can(self::PERMISSION_MANAGE_MODULES, self::PERMISSION_MANAGE_MODULES);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на просмотр финансов
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function canViewFinance(): bool
|
||
{
|
||
return $this->can(self::PERMISSION_VIEW_FINANCE, self::PERMISSION_VIEW_FINANCE);
|
||
}
|
||
|
||
/**
|
||
* Проверка права на удаление организации
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function canDeleteOrganization(): bool
|
||
{
|
||
return $this->isOwner();
|
||
}
|
||
|
||
/**
|
||
* Проверка права на передачу прав владельца
|
||
*
|
||
* @return bool
|
||
*/
|
||
public function canTransferOwnership(): bool
|
||
{
|
||
return $this->isOwner();
|
||
}
|
||
|
||
/**
|
||
* Получение роли для передачи прав владельца (список допустимых ролей)
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getRolesEligibleForOwnershipTransfer(): array
|
||
{
|
||
// Только admin может стать новым владельцем
|
||
return [self::ROLE_ADMIN];
|
||
}
|
||
|
||
/**
|
||
* Получение уровня роли (для сравнения)
|
||
*
|
||
* @param string $role
|
||
* @return int
|
||
*/
|
||
public function getRoleLevel(string $role): int
|
||
{
|
||
return self::ROLE_HIERARCHY[$role] ?? 0;
|
||
}
|
||
|
||
/**
|
||
* Проверка, имеет ли роль достаточно прав для сравнения
|
||
*
|
||
* @param string $role
|
||
* @param string $requiredRole
|
||
* @return bool
|
||
*/
|
||
public function hasRoleLevel(string $role, string $requiredRole): bool
|
||
{
|
||
return $this->getRoleLevel($role) >= $this->getRoleLevel($requiredRole);
|
||
}
|
||
|
||
/**
|
||
* Получение списка доступных ролей для назначения
|
||
*
|
||
* @param string $assignerRole Роль назначающего
|
||
* @return array
|
||
*/
|
||
public function getAvailableRolesForAssignment(string $assignerRole): array
|
||
{
|
||
$allRoles = [self::ROLE_ADMIN, self::ROLE_MANAGER, self::ROLE_GUEST];
|
||
|
||
// Owner может назначать любые роли
|
||
if ($assignerRole === self::ROLE_OWNER) {
|
||
return $allRoles;
|
||
}
|
||
|
||
// Admin не может назначать owner
|
||
if ($assignerRole === self::ROLE_ADMIN) {
|
||
return [self::ROLE_ADMIN, self::ROLE_MANAGER, self::ROLE_GUEST];
|
||
}
|
||
|
||
// Manager и Guest не могут назначать роли
|
||
return [];
|
||
}
|
||
|
||
/**
|
||
* Сброс кэша membership (при переключении организации)
|
||
*
|
||
* @return void
|
||
*/
|
||
public function resetCache(): void
|
||
{
|
||
$this->currentMembership = null;
|
||
}
|
||
|
||
/**
|
||
* Получение текстового названия роли
|
||
*
|
||
* @param string $role
|
||
* @return string
|
||
*/
|
||
public function getRoleLabel(string $role): string
|
||
{
|
||
$labels = [
|
||
self::ROLE_OWNER => 'Владелец',
|
||
self::ROLE_ADMIN => 'Администратор',
|
||
self::ROLE_MANAGER => 'Менеджер',
|
||
self::ROLE_GUEST => 'Гость',
|
||
];
|
||
|
||
return $labels[$role] ?? $role;
|
||
}
|
||
|
||
/**
|
||
* Получение всех ролей с описаниями
|
||
*
|
||
* @return array
|
||
*/
|
||
public static function getAllRoles(): array
|
||
{
|
||
return [
|
||
self::ROLE_OWNER => [
|
||
'label' => 'Владелец',
|
||
'description' => 'Полный доступ к организации',
|
||
'level' => 100,
|
||
],
|
||
self::ROLE_ADMIN => [
|
||
'label' => 'Администратор',
|
||
'description' => 'Управление пользователями и модулями',
|
||
'level' => 75,
|
||
],
|
||
self::ROLE_MANAGER => [
|
||
'label' => 'Менеджер',
|
||
'description' => 'Полный доступ к функционалу модулей',
|
||
'level' => 50,
|
||
],
|
||
self::ROLE_GUEST => [
|
||
'label' => 'Гость',
|
||
'description' => 'Только просмотр данных',
|
||
'level' => 25,
|
||
],
|
||
];
|
||
}
|
||
}
|