Files
breadcrumb-the-shire/lib/Service/Access/PermissionService.php

261 lines
9.1 KiB
PHP
Raw Normal View History

2026-02-04 23:31:53 +01:00
<?php
2026-02-11 19:28:12 +01:00
namespace MintyPHP\Service\Access;
2026-02-04 23:31:53 +01:00
2026-02-11 19:28:12 +01:00
use MintyPHP\Repository\Access\RolePermissionRepository;
use MintyPHP\Repository\Access\UserRoleRepository;
use MintyPHP\Repository\Access\PermissionRepository;
2026-02-04 23:31:53 +01:00
class PermissionService
{
private static array $apiPermissionCache = [];
2026-02-04 23:31:53 +01:00
public const USERS_CREATE = 'users.create';
public const USERS_DELETE = 'users.delete';
public const USERS_VIEW = 'users.view';
2026-02-11 19:28:12 +01:00
public const USERS_VIEW_META = 'users.view_meta';
public const USERS_VIEW_AUDIT = 'users.view_audit';
2026-02-04 23:31:53 +01:00
public const USERS_UPDATE = 'users.update';
public const USERS_ACCESS_PDF = 'users.access_pdf';
2026-02-04 23:31:53 +01:00
public const USERS_SELF_UPDATE = 'users.self_update';
public const USERS_UPDATE_ASSIGNMENTS = 'users.update_assignments';
public const ADDRESS_BOOK_VIEW = 'address_book.view';
public const TENANTS_VIEW = 'tenants.view';
public const TENANTS_CREATE = 'tenants.create';
public const TENANTS_UPDATE = 'tenants.update';
public const TENANTS_SSO_MANAGE = 'tenants.sso_manage';
2026-02-04 23:31:53 +01:00
public const TENANTS_DELETE = 'tenants.delete';
public const DEPARTMENTS_VIEW = 'departments.view';
public const DEPARTMENTS_CREATE = 'departments.create';
public const DEPARTMENTS_UPDATE = 'departments.update';
public const DEPARTMENTS_DELETE = 'departments.delete';
public const DEPARTMENTS_IMPORT = 'departments.import';
2026-02-04 23:31:53 +01:00
public const ROLES_VIEW = 'roles.view';
public const ROLES_CREATE = 'roles.create';
public const ROLES_UPDATE = 'roles.update';
public const ROLES_DELETE = 'roles.delete';
public const PERMISSIONS_VIEW = 'permissions.view';
public const PERMISSIONS_CREATE = 'permissions.create';
public const PERMISSIONS_UPDATE = 'permissions.update';
public const PERMISSIONS_DELETE = 'permissions.delete';
public const SETTINGS_VIEW = 'settings.view';
public const SETTINGS_UPDATE = 'settings.update';
public const IMPORTS_VIEW = 'imports.view';
public const IMPORTS_AUDIT_VIEW = 'imports.audit.view';
public const JOBS_VIEW = 'jobs.view';
public const JOBS_MANAGE = 'jobs.manage';
public const JOBS_RUN_NOW = 'jobs.run_now';
public const USER_LIFECYCLE_AUDIT_VIEW = 'user_lifecycle_audit.view';
public const USERS_IMPORT = 'users.import';
public const USERS_IMPORT_ASSIGNMENTS = 'users.import_assignments';
public const USERS_LIFECYCLE_RESTORE = 'users.lifecycle_restore';
public const CUSTOM_FIELDS_MANAGE = 'custom_fields.manage';
public const CUSTOM_FIELDS_EDIT_VALUES = 'custom_fields.edit_values';
public const API_DOCS_VIEW = 'api_docs.view';
public const DOCS_VIEW = 'docs.view';
2026-02-04 23:31:53 +01:00
public const MAIL_LOG_VIEW = 'mail_log.view';
public const API_AUDIT_VIEW = 'api_audit.view';
2026-02-04 23:31:53 +01:00
public const STATS_VIEW = 'stats.view';
public const API_TOKENS_MANAGE = 'api_tokens.manage';
2026-02-04 23:31:53 +01:00
public static function userHas(int $userId, string $permissionKey): bool
{
$permissionKey = trim((string) $permissionKey);
if ($userId <= 0 || $permissionKey === '') {
return false;
}
$keys = self::getUserPermissions($userId);
return in_array($permissionKey, $keys, true);
}
public static function getUserPermissions(int $userId, bool $refresh = false): array
{
if ($userId <= 0) {
return [];
}
if (self::isApiRequest()) {
if (!$refresh && isset(self::$apiPermissionCache[$userId])) {
return self::$apiPermissionCache[$userId];
}
$roleIds = UserRoleRepository::listRoleIdsByUserId($userId);
$keys = RolePermissionRepository::listPermissionKeysByRoleIds($roleIds);
self::$apiPermissionCache[$userId] = $keys;
return $keys;
}
2026-02-04 23:31:53 +01:00
$cache = $_SESSION['permissions'] ?? null;
if ($refresh) {
$cache = null;
}
if (is_array($cache) && (int) ($cache['user_id'] ?? 0) === $userId) {
$keys = $cache['keys'] ?? [];
if (is_array($keys)) {
return $keys;
}
}
$roleIds = UserRoleRepository::listRoleIdsByUserId($userId);
$keys = RolePermissionRepository::listPermissionKeysByRoleIds($roleIds);
$_SESSION['permissions'] = [
'user_id' => $userId,
'keys' => $keys,
];
return $keys;
}
public static function getCachedPermissions(int $userId): array
{
if ($userId <= 0) {
return [];
}
if (self::isApiRequest()) {
return self::$apiPermissionCache[$userId] ?? [];
}
2026-02-04 23:31:53 +01:00
$cache = $_SESSION['permissions'] ?? null;
if (is_array($cache) && (int) ($cache['user_id'] ?? 0) === $userId) {
$keys = $cache['keys'] ?? [];
return is_array($keys) ? $keys : [];
}
return [];
}
public static function clearUserCache(int $userId): void
{
if (self::isApiRequest()) {
unset(self::$apiPermissionCache[$userId]);
return;
}
2026-02-04 23:31:53 +01:00
$cache = $_SESSION['permissions'] ?? null;
if (is_array($cache) && (int) ($cache['user_id'] ?? 0) === $userId) {
unset($_SESSION['permissions']);
}
}
private static function isApiRequest(): bool
{
return defined('MINTY_API_REQUEST');
}
2026-02-04 23:31:53 +01:00
public static function list(): array
{
return PermissionRepository::list();
}
2026-02-11 19:28:12 +01:00
public static function listActive(): array
{
return PermissionRepository::listActive();
}
2026-02-04 23:31:53 +01:00
public static function listPaged(array $options): array
{
return PermissionRepository::listPaged($options);
}
public static function find(int $id): ?array
{
return PermissionRepository::find($id);
}
public static function findByKey(string $key): ?array
{
return PermissionRepository::findByKey($key);
}
public static function createFromAdmin(array $input): array
{
$form = self::sanitizeBase($input);
$errors = self::validateBase($form);
if ($errors) {
return ['ok' => false, 'errors' => $errors, 'form' => $form];
}
if (PermissionRepository::findByKey($form['key'])) {
return ['ok' => false, 'errors' => [t('Permission key already exists')], 'form' => $form];
}
$createdId = PermissionRepository::create($form);
if (!$createdId) {
return ['ok' => false, 'errors' => [t('Permission can not be created')], 'form' => $form];
}
return ['ok' => true, 'form' => $form, 'id' => (int) $createdId];
}
public static function updateFromAdmin(int $id, array $input): array
{
$form = self::sanitizeBase($input);
$errors = self::validateBase($form);
if ($errors) {
return ['ok' => false, 'errors' => $errors, 'form' => $form];
}
$existing = PermissionRepository::findByKey($form['key']);
if ($existing && (int) ($existing['id'] ?? 0) !== $id) {
return ['ok' => false, 'errors' => [t('Permission key already exists')], 'form' => $form];
}
$updated = PermissionRepository::update($id, $form);
if (!$updated) {
return ['ok' => false, 'errors' => [t('Permission can not be updated')], 'form' => $form];
}
return ['ok' => true, 'form' => $form];
}
public static function deleteById(int $id): array
{
$permission = PermissionRepository::find($id);
if (!$permission) {
return ['ok' => false, 'status' => 404, 'error' => 'not_found'];
}
2026-02-11 19:28:12 +01:00
if ((int) ($permission['is_system'] ?? 0) === 1) {
return ['ok' => false, 'status' => 403, 'error' => 'system_permission_protected'];
}
2026-02-04 23:31:53 +01:00
$deleted = PermissionRepository::delete($id);
if (!$deleted) {
return ['ok' => false, 'status' => 500, 'error' => 'delete_failed'];
}
return ['ok' => true, 'permission' => $permission];
}
private static function sanitizeBase(array $input): array
{
return [
'key' => trim((string) ($input['key'] ?? '')),
'description' => trim((string) ($input['description'] ?? '')),
2026-02-11 19:28:12 +01:00
'active' => self::normalizeActive($input['active'] ?? 1),
'is_system' => self::normalizeFlag($input['is_system'] ?? 0),
2026-02-04 23:31:53 +01:00
];
}
private static function validateBase(array $form): array
{
$errors = [];
if ($form['key'] === '') {
$errors[] = t('Permission key cannot be empty');
} elseif (!preg_match('/^[a-z0-9._-]+$/i', $form['key'])) {
$errors[] = t('Permission key is invalid');
}
return $errors;
}
2026-02-11 19:28:12 +01:00
private static function normalizeActive($value): int
{
$value = strtolower(trim((string) $value));
if (in_array($value, ['0', 'false', 'inactive'], true)) {
return 0;
}
return 1;
}
private static function normalizeFlag($value): int
{
if (is_bool($value)) {
return $value ? 1 : 0;
}
$value = strtolower(trim((string) $value));
if (in_array($value, ['1', 'true', 'yes', 'on'], true)) {
return 1;
}
return 0;
}
2026-02-04 23:31:53 +01:00
}