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,
|
|
],
|
|
];
|
|
}
|
|
}
|