devsofttech_hms_ci4/app/Libraries/RoleRegistry.php
DevSoftTech Server c263b9e4ac feat: add role and session access controls
Add configurable role management, expanded project permission coverage, secured module routes, and single-active-session enforcement with online user administration.
2026-08-09 23:21:16 +05:30

220 lines
7.9 KiB
PHP

<?php
namespace App\Libraries;
use CodeIgniter\Database\BaseConnection;
use InvalidArgumentException;
use RuntimeException;
class RoleRegistry
{
private BaseConnection $db;
public function __construct(?BaseConnection $db = null)
{
$this->db = $db ?? db_connect();
}
public function all(): array
{
$roles = $this->db->table('auth_roles r')
->select('r.*, COUNT(DISTINCT rp.id) AS permission_count, COUNT(DISTINCT gu.id) AS user_count')
->join('auth_role_permissions rp', 'rp.role_id = r.id', 'left')
->join('auth_groups_users gu', 'gu.group = r.alias', 'left')
->groupBy('r.id')
->orderBy('r.is_builtin', 'DESC')
->orderBy('r.title', 'ASC')
->get()
->getResultArray();
return $roles;
}
public function find(int $roleId): ?array
{
$role = $this->db->table('auth_roles')->where('id', $roleId)->get(1)->getRowArray();
if ($role === null) {
return null;
}
$role['permissions'] = array_column(
$this->db->table('auth_role_permissions')->select('permission')->where('role_id', $roleId)->orderBy('permission')->get()->getResultArray(),
'permission'
);
return $role;
}
public function activeGroups(): array
{
$groups = [];
foreach ($this->db->table('auth_roles')->where('is_active', 1)->orderBy('title')->get()->getResultArray() as $role) {
$groups[$role['alias']] = [
'title' => $role['title'],
'description' => $role['description'] ?? '',
];
}
return $groups;
}
public function save(?int $roleId, array $input, int $userId): int
{
$alias = strtolower(trim((string) ($input['alias'] ?? '')));
$title = trim((string) ($input['title'] ?? ''));
$description = trim((string) ($input['description'] ?? ''));
$permissions = $this->validatePermissions((array) ($input['permissions'] ?? []));
if (! preg_match('/^[a-z][a-z0-9_]{2,63}$/', $alias)) {
throw new InvalidArgumentException('Role code must start with a letter and contain only lowercase letters, numbers, and underscores.');
}
if ($title === '' || mb_strlen($title) > 120) {
throw new InvalidArgumentException('Role name is required and must be at most 120 characters.');
}
if (mb_strlen($description) > 255) {
throw new InvalidArgumentException('Description must be at most 255 characters.');
}
$existing = $this->db->table('auth_roles')->where('alias', $alias)->get(1)->getRowArray();
if ($existing !== null && (int) $existing['id'] !== (int) $roleId) {
throw new InvalidArgumentException('Role code is already in use.');
}
$current = $roleId ? $this->find($roleId) : null;
if ($roleId && $current === null) {
throw new InvalidArgumentException('Role not found.');
}
if ($current !== null && (int) $current['is_builtin'] === 1 && $alias !== $current['alias']) {
throw new InvalidArgumentException('Built-in role codes cannot be changed.');
}
if (($current['alias'] ?? '') === 'superadmin') {
$permissions = $current['permissions'];
}
$now = date('Y-m-d H:i:s');
$this->db->transStart();
if ($current === null) {
$this->db->table('auth_roles')->insert([
'alias' => $alias,
'title' => $title,
'description' => $description !== '' ? $description : null,
'is_active' => 1,
'is_builtin' => 0,
'created_by' => $userId ?: null,
'updated_by' => $userId ?: null,
'created_at' => $now,
'updated_at' => $now,
]);
$roleId = (int) $this->db->insertID();
} else {
$this->db->table('auth_roles')->where('id', $roleId)->update([
'title' => $title,
'description' => $description !== '' ? $description : null,
'updated_by' => $userId ?: null,
'updated_at' => $now,
]);
$this->db->table('auth_role_permissions')->where('role_id', $roleId)->delete();
}
foreach ($permissions as $permission) {
$this->db->table('auth_role_permissions')->insert([
'role_id' => $roleId,
'permission' => $permission,
'created_by' => $userId ?: null,
'created_at' => $now,
]);
}
$this->db->transComplete();
if (! $this->db->transStatus()) {
throw new RuntimeException('Unable to save role.');
}
$this->publish();
return (int) $roleId;
}
public function setActive(int $roleId, bool $active, int $userId): void
{
$role = $this->find($roleId);
if ($role === null) {
throw new InvalidArgumentException('Role not found.');
}
if ((int) $role['is_builtin'] === 1) {
throw new InvalidArgumentException('Built-in roles cannot be deactivated.');
}
$this->db->table('auth_roles')->where('id', $roleId)->update([
'is_active' => $active ? 1 : 0,
'updated_by' => $userId ?: null,
'updated_at' => date('Y-m-d H:i:s'),
]);
$this->publish();
}
public function delete(int $roleId): void
{
$role = $this->find($roleId);
if ($role === null) {
throw new InvalidArgumentException('Role not found.');
}
if ((int) $role['is_builtin'] === 1) {
throw new InvalidArgumentException('Built-in roles cannot be deleted.');
}
if ((int) $role['is_active'] === 1) {
throw new InvalidArgumentException('Deactivate the role before deleting it.');
}
$assignments = $this->db->table('auth_groups_users')->where('group', $role['alias'])->countAllResults();
if ($assignments > 0) {
throw new InvalidArgumentException('This role is assigned to users and cannot be deleted.');
}
$this->db->table('auth_roles')->where('id', $roleId)->delete();
$this->publish();
}
public function publish(): void
{
$groups = $this->activeGroups();
$matrix = [];
$rows = $this->db->table('auth_roles r')
->select('r.alias,rp.permission')
->join('auth_role_permissions rp', 'rp.role_id = r.id', 'left')
->where('r.is_active', 1)
->orderBy('r.id')
->get()
->getResultArray();
foreach ($groups as $alias => $_role) {
$matrix[$alias] = [];
}
foreach ($rows as $row) {
if (! empty($row['permission'])) {
$matrix[$row['alias']][] = $row['permission'];
}
}
setting('AuthGroups.groups', $groups);
setting('AuthGroups.matrix', $matrix);
}
private function validatePermissions(array $permissions): array
{
$permissions = array_values(array_unique(array_filter(array_map(static fn ($value): string => strtolower(trim((string) $value)), $permissions))));
$known = array_keys((array) setting('AuthGroups.permissions'));
$allowedWildcards = [];
foreach ($known as $permission) {
$scope = strstr($permission, '.', true);
if ($scope !== false) {
$allowedWildcards[] = $scope . '.*';
}
}
$allowed = array_merge($known, array_unique($allowedWildcards));
foreach ($permissions as $permission) {
if (! in_array($permission, $allowed, true)) {
throw new InvalidArgumentException('Unknown or unsupported permission: ' . $permission);
}
}
return $permissions;
}
}