Перейти до вмісту

ADR-006 - Система дозволів модуля

ADR-006: Система дозволів модуля

Section titled “ADR-006: Система дозволів модуля”

Детальна ієрархічна система дозволів для модулів XOOPS, що забезпечує детальний контроль доступу.


Прийнято - реалізовано в XOOPS 2.5.x і розширено в XOOPS 4.0


Модулі XOOPS потребують гнучкого керування дозволами, які дозволяють:

  1. Дозволи на рівні модуля - Чи може користувач отримати доступ до цього модуля?
  2. Дозволи на рівні об’єкта – чи може користувач отримати доступ до цього конкретного елемента?
  3. Дозволи на рівні дії - Чи може користувач виконати цю дію?
  4. Користувацькі дозволи – чи можуть модулі визначати власні дозволи?

XOOPS 2.5 використовує систему XoopsGroupPermission:

<?php
$perm_handler = xoops_getHandler('groupperm');
$isAllowed = $perm_handler->checkRight(
'modulename',
'action',
$itemId,
$groupId
);
  1. Складні запити – перевірки дозволів вимагають з’єднання з базою даних
  2. Обмежена ієрархія – важко створювати групи дозволів
  3. Погане кешування – немає вбудованого кешування дозволів
  4. Варіанти модулів - кожен модуль реалізується по-різному
  5. Продуктивність - кілька запитів до БД для перевірки дозволів

Впровадити ієрархічну систему дозволів

Section titled “Впровадити ієрархічну систему дозволів”

Створіть стандартизовану кешовану систему дозволів, яка підтримує:

  1. Ієрархічні дозволи - успадкування від батьківських груп
  2. Доступ на основі ролей - зіставлення дозволів для ролей (адміністратор, модератор, користувач, гість)
  3. Дозволи на об’єкти – точне керування кожним елементом
  4. Кешування – доступ до кешу для зменшення кількості запитів
  5. Користувацькі дозволи - модулі визначають свої власні
  6. Аудиторський слід - журнал змін дозволу
User
└── Group 1 (Admin)
└── Permission: admin_module
└── Permission: edit_all_items
└── Permission: delete_all_items
└── Group 2 (Moderator)
└── Permission: moderate_comments
└── Permission: edit_own_items
└── Group 3 (User)
└── Permission: view_published_items
└── Permission: edit_own_items
└── Group 4 (Guest)
└── Permission: view_published_items
graph TB
subgraph "Permission System"
A["Permission Registry<br/>(Define permissions)"]
B["Permission Checker<br/>(Check access)"]
C["Permission Cache<br/>(Improve performance)"]
D["Permission Audit Log<br/>(Track changes)"]
end
subgraph "Data Layer"
E["Group Permissions Table"]
F["User Groups Table"]
G["Permission Definitions"]
end
A --> E
B --> E
B --> C
C --> E
D --> E
F --> B

<?php
// Module defines its permissions in xoops_version.php
$modversion['permissions'] = [
[
'name' => 'module_view',
'description' => 'Can view module',
'level' => 'module',
],
[
'name' => 'item_view',
'description' => 'Can view items',
'level' => 'item',
],
[
'name' => 'item_create',
'description' => 'Can create items',
'level' => 'item',
],
[
'name' => 'item_edit',
'description' => 'Can edit items',
'level' => 'item',
],
[
'name' => 'item_delete',
'description' => 'Can delete items',
'level' => 'item',
],
[
'name' => 'admin_manage',
'description' => 'Can manage module',
'level' => 'admin',
],
];
// Default permissions by group
$modversion['group_permissions'] = [
// Admin group gets all permissions
'1' => [
'module_view' => 1,
'item_view' => 1,
'item_create' => 1,
'item_edit' => 1,
'item_delete' => 1,
'admin_manage' => 1,
],
// User group
'3' => [
'module_view' => 1,
'item_view' => 1,
'item_create' => 1,
'item_edit' => 0,
'item_delete' => 0,
'admin_manage' => 0,
],
// Guest group
'4' => [
'module_view' => 1,
'item_view' => 1,
'item_create' => 0,
'item_edit' => 0,
'item_delete' => 0,
'admin_manage' => 0,
],
];
<?php
declare(strict_types=1);
namespace XoopsCore\Permission;
class PermissionChecker
{
private PermissionCache $cache;
private PermissionRepository $repository;
public function hasPermission(
User $user,
string $permissionName,
?int $itemId = null
): bool {
// Check cache first
$cacheKey = "perm_{$user->getId()}_{$permissionName}_{$itemId}";
if ($this->cache->has($cacheKey)) {
return $this->cache->get($cacheKey);
}
$hasPermission = false;
// Check all user groups
foreach ($user->getGroups() as $group) {
if ($this->checkGroupPermission($group, $permissionName, $itemId)) {
$hasPermission = true;
break;
}
}
// Cache result
$this->cache->set($cacheKey, $hasPermission, 3600);
// Log high-level access checks
if ($hasPermission && $this->shouldAuditLog($permissionName)) {
$this->auditLog('PERMISSION_CHECKED', [
'user_id' => $user->getId(),
'permission' => $permissionName,
'item_id' => $itemId,
'result' => 'ALLOWED',
]);
}
return $hasPermission;
}
private function checkGroupPermission(
Group $group,
string $permissionName,
?int $itemId = null
): bool {
$sql = 'SELECT COUNT(*) FROM ' . $this->table . '
WHERE groupid = ?
AND permission = ?
AND itemid = ?
AND granted = 1';
$stmt = $this->db->prepare($sql);
$stmt->execute([$group->getId(), $permissionName, $itemId ?? 0]);
return $stmt->fetchColumn() > 0;
}
}
<?php
// Different permission levels with different scopes
class PermissionLevel
{
// Module-level: Affects entire module
public const LEVEL_MODULE = 'module';
// Admin-level: Admin panel access
public const LEVEL_ADMIN = 'admin';
// Item-level: Specific objects/items
public const LEVEL_ITEM = 'item';
// Field-level: Specific object fields
public const LEVEL_FIELD = 'field';
// Action-level: Specific actions/operations
public const LEVEL_ACTION = 'action';
}

4. Дозволи на рівні об’єкта

Section titled “4. Дозволи на рівні об’єкта”
<?php
// Fine-grained control for specific items
class Item extends XoopsObject
{
/**
* Check if user can view this item
*/
public function canView(User $user): bool
{
// Public items anyone can view
if ($this->getVar('status') === 'published') {
return true;
}
// Owner can always view their items
if ($this->getVar('user_id') === $user->getId()) {
return true;
}
// Check group permissions
$permChecker = xoops_getActiveModule()->getPermissionChecker();
return $permChecker->hasPermission(
$user,
'item_view',
$this->getVar('id')
);
}
public function canEdit(User $user): bool
{
// Owner can edit their items
if ($this->getVar('user_id') === $user->getId()) {
return $permChecker->hasPermission($user, 'item_edit', $this->getVar('id'));
}
// Check if user can edit all items
return $permChecker->hasPermission($user, 'item_edit_all', $this->getVar('id'));
}
public function canDelete(User $user): bool
{
return $permChecker->hasPermission($user, 'item_delete', $this->getVar('id'));
}
}

5. Використання в контролерах

Section titled “5. Використання в контролерах”
<?php
// Example: Article controller
class ArticleController
{
private PermissionChecker $permChecker;
public function view(int $id, User $user): Response
{
$article = $this->repository->find($id);
// Check permission
if (!$article->canView($user)) {
throw new AccessDeniedException('Cannot view this article');
}
return new HtmlResponse($this->renderArticle($article));
}
public function edit(int $id, User $user): Response
{
$article = $this->repository->find($id);
// Check permission
if (!$article->canEdit($user)) {
throw new AccessDeniedException('Cannot edit this article');
}
// Handle form submission
if ($this->request->isMethod('POST')) {
$article->setVar('title', $this->request->getPost('title'));
$article->setVar('content', $this->request->getPost('content'));
$this->repository->insert($article);
$this->auditLog('ARTICLE_EDITED', ['id' => $id, 'user_id' => $user->getId()]);
// Invalidate permission cache
$this->permChecker->clearCache($user->getId());
return new RedirectResponse('/article/' . $id);
}
return new HtmlResponse($this->renderForm($article));
}
public function delete(int $id, User $user): Response
{
$article = $this->repository->find($id);
if (!$article->canDelete($user)) {
throw new AccessDeniedException('Cannot delete this article');
}
$this->repository->delete($article);
$this->auditLog('ARTICLE_DELETED', ['id' => $id, 'user_id' => $user->getId()]);
// Invalidate cache
$this->permChecker->clearCache($user->getId());
return new JsonResponse(['success' => true]);
}
}

  1. Детальний контроль - точно налаштоване керування дозволами
  2. Стандартизований - Послідовність між модулями
  3. Cached - Покращена продуктивність завдяки кешуванню
  4. Auditable - відстежуйте, хто що змінив
  5. Гнучкий - підтримка спеціальних дозволів
  6. Масштабований - обробляє складні ієрархії дозволів
  7. Testable - Легко тестувати модуль
  1. Складність – більше коду для керування
  2. Накладні витрати на базу даних – більше таблиць і об’єднань
  3. Недійсність кешу - необхідно очистити кеш після змін
  4. Крива навчання - розробники повинні розуміти систему
  5. Продуктивність - якщо кеш не налаштовано належним чином
РизикТяжкістьПом’якшення
Надто складні дозволиСереднійХороші параметри за замовчуванням, документація
Кешувати застарілі даніВисокийTTL, розумне визнання недійсним
Регресія продуктивностіСереднійБенчмарк, оптимізація запитів
Обхід дозволуВисокийАудит безпеки, тести

Шаблони оформлення дозволів

Section titled “Шаблони оформлення дозволів”

Шаблон 1: Дозволи на основі власника

Section titled “Шаблон 1: Дозволи на основі власника”
<?php
// User can edit their own items but not others'
public function canEdit(User $user): bool
{
// Owner can always edit
if ($this->isOwner($user)) {
return true;
}
// Check group permissions for editing others' items
return $this->permChecker->hasPermission($user, 'edit_all_items');
}
private function isOwner(User $user): bool
{
return $this->getVar('user_id') === $user->getId();
}

Шаблон 2: Дозволи на основі статусу

Section titled “Шаблон 2: Дозволи на основі статусу”
<?php
// Different permissions based on status
public function canView(User $user): bool
{
switch ($this->getVar('status')) {
case 'published':
// Anyone with module permission can view
return $this->permChecker->hasPermission($user, 'item_view');
case 'draft':
// Only owner or admin can view
return $this->isOwner($user) ||
$this->permChecker->hasPermission($user, 'admin_manage');
case 'archived':
// Only admin can view
return $this->permChecker->hasPermission($user, 'admin_manage');
default:
return false;
}
}

Шаблон 3: Дозволи на основі ролей

Section titled “Шаблон 3: Дозволи на основі ролей”
<?php
// Check against specific roles
public function hasAdminRole(User $user): bool
{
return $user->getGroups()->contains('admin_group');
}
public function hasModeratorRole(User $user): bool
{
return $user->getGroups()->contains('moderator_group') ||
$this->hasAdminRole($user);
}
public function canModerate(User $user): bool
{
return $this->hasModeratorRole($user);
}

  • ADR-001: Модульна архітектура - Модулі визначають дозволи
  • ADR-004: Система безпеки - Основа безпеки
  • ADR-005: проміжне програмне забезпечення - може застосовувати дозволи


Контрольний список впровадження

Section titled “Контрольний список впровадження”
  • Визначення стандартних рівнів дозволів
  • [] Створення класу PermissionChecker
  • Реалізація стратегії кешування
  • Додати журнал аудиту
  • Створення допоміжних функцій
  • Написати комплексні контрольні роботи
  • Документ для розробників
  • Оновити всі модулі
  • Оптимізація продуктивності
  • Огляд безпеки

ВерсіяДатаЗміни
1.0.02024-01-28Вихідний документ

#xoops #adr #permissions #authorization #rbac #security