From a92e5aee3cea0729fb92d5402a62b05339b54e26 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 24 May 2026 19:02:18 +0200 Subject: [PATCH 01/12] Apply conservative Rector cleanup --- src/Auth/AclAdapter/DbAclAdapter.php | 3 +- src/Auth/AllowAdapter/DbAllowAdapter.php | 3 +- src/Controller/Admin/AclController.php | 34 +++++++++----------- src/Controller/Admin/AllowController.php | 4 +-- src/Controller/Admin/AppController.php | 3 -- src/Controller/Admin/DashboardController.php | 4 +-- src/Controller/Admin/ResourcesController.php | 30 ++++++++--------- src/Controller/Admin/RolesController.php | 6 ++-- src/Controller/Admin/ScopesController.php | 6 ++-- src/Controller/Admin/SyncController.php | 4 +-- src/Identity/EntityIdentity.php | 4 +-- src/Model/Table/RolesTable.php | 8 ++--- src/Model/Table/ScopesTable.php | 4 +-- src/Policy/TinyAuthPolicy.php | 8 ++--- src/Policy/TinyAuthResolver.php | 2 +- src/Service/FeatureService.php | 16 ++++----- src/Service/ImportExportService.php | 10 +++--- src/Service/RoleSourceService.php | 4 +-- tests/TestCase/CspComplianceTest.php | 4 +-- tests/schema.php | 2 +- 20 files changed, 70 insertions(+), 89 deletions(-) diff --git a/src/Auth/AclAdapter/DbAclAdapter.php b/src/Auth/AclAdapter/DbAclAdapter.php index 9d813a1..92a3290 100644 --- a/src/Auth/AclAdapter/DbAclAdapter.php +++ b/src/Auth/AclAdapter/DbAclAdapter.php @@ -83,9 +83,8 @@ protected function buildKey(?string $plugin, ?string $prefix, string $controller if ($prefix) { $key .= $prefix . '/'; } - $key .= $controller; - return $key; + return $key . $controller; } } diff --git a/src/Auth/AllowAdapter/DbAllowAdapter.php b/src/Auth/AllowAdapter/DbAllowAdapter.php index ff4f470..5bae49c 100644 --- a/src/Auth/AllowAdapter/DbAllowAdapter.php +++ b/src/Auth/AllowAdapter/DbAllowAdapter.php @@ -61,9 +61,8 @@ protected function buildKey(?string $plugin, ?string $prefix, string $controller if ($prefix) { $key .= $prefix . '/'; } - $key .= $controller; - return $key; + return $key . $controller; } } diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index 280b85b..66456ec 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -38,7 +38,7 @@ public function index(): void { $permissions = $this->buildCellStates($roles, array_map(static fn ($action) => (int)$action->id, $actions)); } - $this->set(compact('tree', 'roles', 'selectedController', 'actions', 'permissions')); + $this->set(['tree' => $tree, 'roles' => $roles, 'selectedController' => $selectedController, 'actions' => $actions, 'permissions' => $permissions]); } /** @@ -59,7 +59,7 @@ public function toggle(): ?Response { if (!in_array($type, ['none', 'allow', 'deny'], true)) { throw new BadRequestException('Invalid permission type'); } - if (!in_array($roleId, array_values((new RoleSourceService())->getRoles()), true)) { + if (!in_array($roleId, (new RoleSourceService())->getRoles(), true)) { throw new BadRequestException('Invalid role'); } @@ -70,22 +70,19 @@ public function toggle(): ?Response { ->first(); if ($type === 'none') { - if ($existing) { - if (!$permissionsTable->delete($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to delete permission'); - } - } - } else { - /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ - if ($existing) { - $existing->type = $type; - $existing->description = $description; - if (!$permissionsTable->save($existing)) { + if ($existing && !$permissionsTable->delete($existing)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to delete permission'); + } + } elseif ($existing) { + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing->type = $type; + $existing->description = $description; + if (!$permissionsTable->save($existing)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to update permission'); } - } else { + } else { $permission = $permissionsTable->newEntity([ 'action_id' => $actionId, 'role_id' => $roleId, @@ -97,7 +94,6 @@ public function toggle(): ?Response { $this->set('error', 'Failed to save permission'); } } - } $roles = (new RoleSourceService())->getRoleEntities(); $permissions = $this->buildCellStates($roles, [$actionId]); @@ -105,7 +101,7 @@ public function toggle(): ?Response { // Return updated cell HTML $this->viewBuilder()->disableAutoLayout(); - $this->set(compact('cell')); + $this->set(['cell' => $cell]); return $this->render('toggle_cell'); } @@ -117,7 +113,7 @@ public function search(): void { $this->viewBuilder()->disableAutoLayout(); $q = $this->request->getQuery('q', ''); - $q = substr($q, 0, 100); // Limit search query length + $q = substr((string) $q, 0, 100); // Limit search query length $results = ['controllers' => [], 'actions' => [], 'roles' => []]; if (strlen($q) >= 2) { @@ -244,7 +240,7 @@ protected function buildInheritedPermissions(array $roles, array $actionIds, arr $effectiveAcl = (new HierarchyService())->applyInheritance($acl, $availableRoles); $inheritedPermissions = []; foreach ($actionIds as $actionId) { - foreach (($effectiveAcl['selected']['allow'][(string)$actionId] ?? []) as $alias => $roleId) { + foreach (($effectiveAcl['selected']['allow'][(string)$actionId] ?? []) as $roleId) { if (($directPermissions[$actionId][$roleId] ?? null) === 'allow') { continue; } diff --git a/src/Controller/Admin/AllowController.php b/src/Controller/Admin/AllowController.php index 46721ba..7cf6340 100644 --- a/src/Controller/Admin/AllowController.php +++ b/src/Controller/Admin/AllowController.php @@ -37,7 +37,7 @@ public function index(): void { $controllers = $query->all()->toArray(); - $this->set(compact('controllers', 'filter')); + $this->set(['controllers' => $controllers, 'filter' => $filter]); } /** @@ -71,7 +71,7 @@ public function toggle(): ?Response { } $this->viewBuilder()->disableAutoLayout(); - $this->set(compact('action')); + $this->set(['action' => $action]); return $this->render('toggle_cell'); } diff --git a/src/Controller/Admin/AppController.php b/src/Controller/Admin/AppController.php index 731d466..9f1563a 100644 --- a/src/Controller/Admin/AppController.php +++ b/src/Controller/Admin/AppController.php @@ -109,9 +109,6 @@ public function beforeFilter(EventInterface $event): void { private function runGate(Closure $gate): void { try { $allowed = $gate() === true; - } catch (ForbiddenException $e) { - // Caller explicitly chose the 403 path — respect it. - throw $e; } catch (Throwable $e) { // Convert any other failure (broken callable, transient DB // error in a role lookup, etc.) to a generic 403. Logging diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 1d57cb0..701ee2e 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -52,7 +52,7 @@ public function index(): void { ->all() ->toArray(); - $this->set(compact('stats', 'features', 'recentControllers')); + $this->set(['stats' => $stats, 'features' => $features, 'recentControllers' => $recentControllers]); } /** @@ -64,7 +64,7 @@ public function concepts(): void { $featureService = new FeatureService(); $features = $featureService->getEnabledFeatures(); - $this->set(compact('features')); + $this->set(['features' => $features]); } } diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index bc3e653..98d95e4 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -70,7 +70,7 @@ public function index(): void { } } - $this->set(compact('resources', 'roles', 'scopes', 'selectedResource', 'abilities', 'permissions')); + $this->set(['resources' => $resources, 'roles' => $roles, 'scopes' => $scopes, 'selectedResource' => $selectedResource, 'abilities' => $abilities, 'permissions' => $permissions]); } /** @@ -89,7 +89,7 @@ public function toggle(): ?Response { if (!in_array($type, ['none', 'allow', 'deny'], true)) { throw new BadRequestException('Invalid permission type'); } - if (!in_array($roleId, array_values((new RoleSourceService())->getRoles()), true)) { + if (!in_array($roleId, (new RoleSourceService())->getRoles(), true)) { throw new BadRequestException('Invalid role'); } @@ -108,22 +108,19 @@ public function toggle(): ?Response { ->first(); if ($type === 'none') { - if ($existing) { - if (!$resourceAclTable->delete($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to remove permission'); - } - } - } else { - /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ - if ($existing) { - $existing->type = $type; - $existing->scope_id = $scopeId; - if (!$resourceAclTable->save($existing)) { + if ($existing && !$resourceAclTable->delete($existing)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to remove permission'); + } + } elseif ($existing) { + /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ + $existing->type = $type; + $existing->scope_id = $scopeId; + if (!$resourceAclTable->save($existing)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to update permission'); } - } else { + } else { $permission = $resourceAclTable->newEntity([ 'resource_ability_id' => $abilityId, 'role_id' => $roleId, @@ -135,7 +132,6 @@ public function toggle(): ?Response { $this->set('error', 'Failed to create permission'); } } - } // Return updated cell — include all scopes so the menu can be re-rendered $scopesTable = $this->fetchTable('TinyAuthBackend.Scopes'); @@ -143,7 +139,7 @@ public function toggle(): ?Response { $scopes = $scopesTable->find()->orderBy(['name' => 'ASC'])->toArray(); $this->viewBuilder()->disableAutoLayout(); - $this->set(compact('abilityId', 'roleId', 'type', 'scope', 'scopes')); + $this->set(['abilityId' => $abilityId, 'roleId' => $roleId, 'type' => $type, 'scope' => $scope, 'scopes' => $scopes]); return $this->render('toggle_cell'); } diff --git a/src/Controller/Admin/RolesController.php b/src/Controller/Admin/RolesController.php index dabe4c4..c102bbb 100644 --- a/src/Controller/Admin/RolesController.php +++ b/src/Controller/Admin/RolesController.php @@ -33,7 +33,7 @@ public function index(): void { $hierarchy = $rolesTable->findHierarchy(); } - $this->set(compact('isManaged', 'hierarchy', 'roles')); + $this->set(['isManaged' => $isManaged, 'hierarchy' => $hierarchy, 'roles' => $roles]); } /** @@ -64,7 +64,7 @@ public function add(): ?Response { ->orderBy(['name' => 'ASC']) ->toArray(); - $this->set(compact('role', 'parents')); + $this->set(['role' => $role, 'parents' => $parents]); return $this->render('form'); } @@ -101,7 +101,7 @@ public function edit(int $id): ?Response { ->orderBy(['name' => 'ASC']) ->toArray(); - $this->set(compact('role', 'parents')); + $this->set(['role' => $role, 'parents' => $parents]); return $this->render('form'); } diff --git a/src/Controller/Admin/ScopesController.php b/src/Controller/Admin/ScopesController.php index d970a4a..e7184b2 100644 --- a/src/Controller/Admin/ScopesController.php +++ b/src/Controller/Admin/ScopesController.php @@ -17,7 +17,7 @@ public function index(): void { ->all() ->toArray(); - $this->set(compact('scopes')); + $this->set(['scopes' => $scopes]); } /** @@ -37,7 +37,7 @@ public function add(): ?Response { $this->Flash->error(__d('tinyauth_backend', 'Could not save scope.')); } - $this->set(compact('scope')); + $this->set(['scope' => $scope]); return $this->render('form'); } @@ -60,7 +60,7 @@ public function edit(int $id): ?Response { $this->Flash->error(__d('tinyauth_backend', 'Could not update scope.')); } - $this->set(compact('scope')); + $this->set(['scope' => $scope]); return $this->render('form'); } diff --git a/src/Controller/Admin/SyncController.php b/src/Controller/Admin/SyncController.php index 28b949a..e19f538 100644 --- a/src/Controller/Admin/SyncController.php +++ b/src/Controller/Admin/SyncController.php @@ -54,7 +54,7 @@ public function controllers(): ?Response { $diff[] = array_merge($item, ['status' => $status]); } - $this->set(compact('diff')); + $this->set(['diff' => $diff]); return null; } @@ -94,7 +94,7 @@ public function resources(): ?Response { $diff[] = array_merge($item, ['status' => $status]); } - $this->set(compact('diff')); + $this->set(['diff' => $diff]); return null; } diff --git a/src/Identity/EntityIdentity.php b/src/Identity/EntityIdentity.php index a3b87a7..dfc95c1 100644 --- a/src/Identity/EntityIdentity.php +++ b/src/Identity/EntityIdentity.php @@ -79,7 +79,7 @@ public function can(string $action, mixed $resource): bool { * @inheritDoc */ public function canResult(string $action, mixed $resource): ResultInterface { - if ($this->service === null) { + if (!$this->service instanceof \Authorization\AuthorizationServiceInterface) { throw new BadMethodCallException( 'EntityIdentity::canResult() requires an AuthorizationService. ' . 'Construct EntityIdentity with a service, or call can() instead.', @@ -93,7 +93,7 @@ public function canResult(string $action, mixed $resource): ResultInterface { * @inheritDoc */ public function applyScope(string $action, mixed $resource, mixed ...$optionalArgs): mixed { - if ($this->service === null) { + if (!$this->service instanceof \Authorization\AuthorizationServiceInterface) { return $resource; } diff --git a/src/Model/Table/RolesTable.php b/src/Model/Table/RolesTable.php index 9113728..e8bbb03 100644 --- a/src/Model/Table/RolesTable.php +++ b/src/Model/Table/RolesTable.php @@ -120,12 +120,8 @@ public function validationDefault(Validator $validator): Validator { if (!$value) { return true; } - // Can't be your own parent - if (isset($context['data']['id']) && (int)$value === (int)$context['data']['id']) { - return false; - } - - return true; + // Can't be your own parent + return !(isset($context['data']['id']) && (int)$value === (int)$context['data']['id']); }, 'message' => __d('tinyauth_backend', 'A role cannot be its own parent.'), ]) diff --git a/src/Model/Table/ScopesTable.php b/src/Model/Table/ScopesTable.php index e7ed499..8b5d86d 100644 --- a/src/Model/Table/ScopesTable.php +++ b/src/Model/Table/ScopesTable.php @@ -58,7 +58,7 @@ public function validationDefault(Validator $validator): Validator { ->requirePresence('entity_field', 'create') ->notEmptyString('entity_field') ->add('entity_field', 'validFieldName', [ - 'rule' => ['custom', '/^[a-zA-Z_][a-zA-Z0-9_]*$/'], + 'rule' => ['custom', '/^[a-zA-Z_]\w*$/'], 'message' => __d('tinyauth_backend', 'Invalid field name. Use only letters, numbers, and underscores.'), ]); @@ -68,7 +68,7 @@ public function validationDefault(Validator $validator): Validator { ->requirePresence('user_field', 'create') ->notEmptyString('user_field') ->add('user_field', 'validFieldName', [ - 'rule' => ['custom', '/^[a-zA-Z_][a-zA-Z0-9_]*$/'], + 'rule' => ['custom', '/^[a-zA-Z_]\w*$/'], 'message' => __d('tinyauth_backend', 'Invalid field name. Use only letters, numbers, and underscores.'), ]); diff --git a/src/Policy/TinyAuthPolicy.php b/src/Policy/TinyAuthPolicy.php index b157586..3369f6b 100644 --- a/src/Policy/TinyAuthPolicy.php +++ b/src/Policy/TinyAuthPolicy.php @@ -77,7 +77,7 @@ public function __construct(?TinyAuthService $tinyAuth = null) { */ public function before(?IdentityInterface $identity, mixed $resource, string $action): ?bool { $user = $this->resolveUser($identity); - if ($user === null) { + if (!$user instanceof \Cake\Datasource\EntityInterface) { return false; } @@ -101,7 +101,7 @@ public function before(?IdentityInterface $identity, mixed $resource, string $ac */ public function can(?IdentityInterface $identity, string $ability, EntityInterface $entity): bool { $user = $this->resolveUser($identity); - if ($user === null) { + if (!$user instanceof \Cake\Datasource\EntityInterface) { return false; } @@ -235,7 +235,7 @@ public function scopeView(?IdentityInterface $identity, SelectQuery $query): Sel */ protected function applyScopeConditions(?IdentityInterface $identity, SelectQuery $query, string $ability): SelectQuery { $user = $this->resolveUser($identity); - if ($user === null) { + if (!$user instanceof \Cake\Datasource\EntityInterface) { return $query->where(['1 = 0']); } @@ -266,7 +266,7 @@ protected function applyScopeConditions(?IdentityInterface $identity, SelectQuer * @return \Cake\Datasource\EntityInterface|null */ protected function resolveUser(?IdentityInterface $identity): ?EntityInterface { - if ($identity === null) { + if (!$identity instanceof \Authorization\IdentityInterface) { return null; } $data = $identity->getOriginalData(); diff --git a/src/Policy/TinyAuthResolver.php b/src/Policy/TinyAuthResolver.php index 2b62259..f642f47 100644 --- a/src/Policy/TinyAuthResolver.php +++ b/src/Policy/TinyAuthResolver.php @@ -100,7 +100,7 @@ public function __construct(array $allowedClasses = [], ?TinyAuthPolicy $policy public function getPolicy(mixed $resource): object { $classes = $this->resolveCandidateClasses($resource); if ($classes === []) { - throw new MissingPolicyException([is_object($resource) ? $resource::class : gettype($resource)]); + throw new MissingPolicyException([get_debug_type($resource)]); } if (!$this->isAllowed($classes)) { diff --git a/src/Service/FeatureService.php b/src/Service/FeatureService.php index 30af370..8c7dc99 100644 --- a/src/Service/FeatureService.php +++ b/src/Service/FeatureService.php @@ -84,16 +84,14 @@ public function getEnabledFeatures(): array { } elseif ($configValue === false) { // Force disabled $result[$feature] = false; - } else { - // Auto-detect - if ($feature === 'roles') { - // Special handling: roles available from external source OR table - $hasExternalSource = Configure::read('TinyAuthBackend.roleSource') !== null; - $result[$feature] = $hasExternalSource || $this->tableExists($table); - } else { + } elseif ($feature === 'roles') { + // Auto-detect + // Special handling: roles available from external source OR table + $hasExternalSource = Configure::read('TinyAuthBackend.roleSource') !== null; + $result[$feature] = $hasExternalSource || $this->tableExists($table); + } else { $result[$feature] = $this->tableExists($table); } - } } static::$cachedFeatures = $result; @@ -184,7 +182,7 @@ protected function tableExists(string $tableName): bool { $tables = $connection->getSchemaCollection()->listTables(); return in_array($tableName, $tables, true); - } catch (Exception $e) { + } catch (Exception) { // Treat connection errors as "table missing" - conservative approach // Logging could be added here if debugging is needed return false; diff --git a/src/Service/ImportExportService.php b/src/Service/ImportExportService.php index 73989d6..116213d 100644 --- a/src/Service/ImportExportService.php +++ b/src/Service/ImportExportService.php @@ -185,11 +185,11 @@ public function importIni(string $content, string $mode = 'merge'): array { $prefix = null; $name = $controllerPath; - if (str_contains($name, '.')) { - [$plugin, $name] = explode('.', $name, 2); + if (str_contains((string) $name, '.')) { + [$plugin, $name] = explode('.', (string) $name, 2); } - if (str_contains($name, '/')) { - $parts = explode('/', $name); + if (str_contains((string) $name, '/')) { + $parts = explode('/', (string) $name); $name = array_pop($parts); $prefix = implode('/', $parts); } @@ -240,7 +240,7 @@ public function importIni(string $content, string $mode = 'merge'): array { } // Parse roles - $roleAliases = array_map('trim', explode(',', $rolesList)); + $roleAliases = array_map('trim', explode(',', (string) $rolesList)); foreach ($roleAliases as $alias) { if (!isset($roleLookup[$alias])) { $result['errors'][] = "Unknown role: {$alias}"; diff --git a/src/Service/RoleSourceService.php b/src/Service/RoleSourceService.php index 2ef8ce9..ec71981 100644 --- a/src/Service/RoleSourceService.php +++ b/src/Service/RoleSourceService.php @@ -157,7 +157,7 @@ protected function getRolesFromTable(): array { ->toArray(); return $roles; - } catch (Exception $e) { + } catch (Exception) { return []; } } @@ -217,7 +217,7 @@ protected function syncExternalRoles(array $roles): void { try { /** @var \TinyAuthBackend\Model\Table\RolesTable $rolesTable */ $rolesTable = TableRegistry::getTableLocator()->get('TinyAuthBackend.Roles'); - } catch (Exception $e) { + } catch (Exception) { return; } diff --git a/tests/TestCase/CspComplianceTest.php b/tests/TestCase/CspComplianceTest.php index 3d7c2bb..5dea9f5 100644 --- a/tests/TestCase/CspComplianceTest.php +++ b/tests/TestCase/CspComplianceTest.php @@ -31,7 +31,7 @@ class CspComplianceTest extends TestCase { * @return void */ public function testNoAlpineDirectivesInTemplates(): void { - $offenders = $this->scanTemplates(static::ALPINE_PATTERN); + $offenders = $this->scanTemplates(self::ALPINE_PATTERN); $this->assertSame([], $offenders, "Alpine directives found:\n" . implode("\n", $offenders)); } @@ -39,7 +39,7 @@ public function testNoAlpineDirectivesInTemplates(): void { * @return void */ public function testNoInlineEventHandlersInTemplates(): void { - $offenders = $this->scanTemplates(static::INLINE_HANDLER_PATTERN); + $offenders = $this->scanTemplates(self::INLINE_HANDLER_PATTERN); $this->assertSame([], $offenders, "Inline event handlers found:\n" . implode("\n", $offenders)); } diff --git a/tests/schema.php b/tests/schema.php index 2b6fd5e..d246930 100644 --- a/tests/schema.php +++ b/tests/schema.php @@ -21,7 +21,7 @@ $tableObject = (new ReflectionClass($class))->getProperty('table'); $tableName = $tableObject->getDefaultValue(); - } catch (ReflectionException $e) { + } catch (ReflectionException) { continue; } From c12b3c5cdcaebc2250ba1e9f46bcd3522f66f95b Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 24 May 2026 19:13:03 +0200 Subject: [PATCH 02/12] Run PHPCS cleanup --- src/Controller/Admin/AclController.php | 32 ++++++++++---------- src/Controller/Admin/ResourcesController.php | 30 +++++++++--------- src/Identity/EntityIdentity.php | 4 +-- src/Model/Table/RolesTable.php | 5 +-- src/Policy/TinyAuthPolicy.php | 8 ++--- src/Service/FeatureService.php | 12 ++++---- src/Service/ImportExportService.php | 10 +++--- tests/TestCase/CspComplianceTest.php | 4 +-- 8 files changed, 53 insertions(+), 52 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index 66456ec..bc0c3ec 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -70,30 +70,30 @@ public function toggle(): ?Response { ->first(); if ($type === 'none') { - if ($existing && !$permissionsTable->delete($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to delete permission'); - } - } elseif ($existing) { - /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ - $existing->type = $type; - $existing->description = $description; - if (!$permissionsTable->save($existing)) { + if ($existing && !$permissionsTable->delete($existing)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to delete permission'); + } + } elseif ($existing) { + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing->type = $type; + $existing->description = $description; + if (!$permissionsTable->save($existing)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to update permission'); - } - } else { + } + } else { $permission = $permissionsTable->newEntity([ 'action_id' => $actionId, 'role_id' => $roleId, 'type' => $type, 'description' => $description, ]); - if (!$permissionsTable->save($permission)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to save permission'); - } + if (!$permissionsTable->save($permission)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to save permission'); } + } $roles = (new RoleSourceService())->getRoleEntities(); $permissions = $this->buildCellStates($roles, [$actionId]); @@ -113,7 +113,7 @@ public function search(): void { $this->viewBuilder()->disableAutoLayout(); $q = $this->request->getQuery('q', ''); - $q = substr((string) $q, 0, 100); // Limit search query length + $q = substr((string)$q, 0, 100); // Limit search query length $results = ['controllers' => [], 'actions' => [], 'roles' => []]; if (strlen($q) >= 2) { diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index 98d95e4..64be945 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -108,30 +108,30 @@ public function toggle(): ?Response { ->first(); if ($type === 'none') { - if ($existing && !$resourceAclTable->delete($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to remove permission'); - } - } elseif ($existing) { - /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ - $existing->type = $type; - $existing->scope_id = $scopeId; - if (!$resourceAclTable->save($existing)) { + if ($existing && !$resourceAclTable->delete($existing)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to remove permission'); + } + } elseif ($existing) { + /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ + $existing->type = $type; + $existing->scope_id = $scopeId; + if (!$resourceAclTable->save($existing)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to update permission'); - } - } else { + } + } else { $permission = $resourceAclTable->newEntity([ 'resource_ability_id' => $abilityId, 'role_id' => $roleId, 'type' => $type, 'scope_id' => $scopeId, ]); - if (!$resourceAclTable->save($permission)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to create permission'); - } + if (!$resourceAclTable->save($permission)) { + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to create permission'); } + } // Return updated cell — include all scopes so the menu can be re-rendered $scopesTable = $this->fetchTable('TinyAuthBackend.Scopes'); diff --git a/src/Identity/EntityIdentity.php b/src/Identity/EntityIdentity.php index dfc95c1..764d0ad 100644 --- a/src/Identity/EntityIdentity.php +++ b/src/Identity/EntityIdentity.php @@ -79,7 +79,7 @@ public function can(string $action, mixed $resource): bool { * @inheritDoc */ public function canResult(string $action, mixed $resource): ResultInterface { - if (!$this->service instanceof \Authorization\AuthorizationServiceInterface) { + if (!$this->service instanceof AuthorizationServiceInterface) { throw new BadMethodCallException( 'EntityIdentity::canResult() requires an AuthorizationService. ' . 'Construct EntityIdentity with a service, or call can() instead.', @@ -93,7 +93,7 @@ public function canResult(string $action, mixed $resource): ResultInterface { * @inheritDoc */ public function applyScope(string $action, mixed $resource, mixed ...$optionalArgs): mixed { - if (!$this->service instanceof \Authorization\AuthorizationServiceInterface) { + if (!$this->service instanceof AuthorizationServiceInterface) { return $resource; } diff --git a/src/Model/Table/RolesTable.php b/src/Model/Table/RolesTable.php index e8bbb03..f4e42a7 100644 --- a/src/Model/Table/RolesTable.php +++ b/src/Model/Table/RolesTable.php @@ -120,8 +120,9 @@ public function validationDefault(Validator $validator): Validator { if (!$value) { return true; } - // Can't be your own parent - return !(isset($context['data']['id']) && (int)$value === (int)$context['data']['id']); + + // Can't be your own parent + return !(isset($context['data']['id']) && (int)$value === (int)$context['data']['id']); }, 'message' => __d('tinyauth_backend', 'A role cannot be its own parent.'), ]) diff --git a/src/Policy/TinyAuthPolicy.php b/src/Policy/TinyAuthPolicy.php index 3369f6b..94412f6 100644 --- a/src/Policy/TinyAuthPolicy.php +++ b/src/Policy/TinyAuthPolicy.php @@ -77,7 +77,7 @@ public function __construct(?TinyAuthService $tinyAuth = null) { */ public function before(?IdentityInterface $identity, mixed $resource, string $action): ?bool { $user = $this->resolveUser($identity); - if (!$user instanceof \Cake\Datasource\EntityInterface) { + if (!$user instanceof EntityInterface) { return false; } @@ -101,7 +101,7 @@ public function before(?IdentityInterface $identity, mixed $resource, string $ac */ public function can(?IdentityInterface $identity, string $ability, EntityInterface $entity): bool { $user = $this->resolveUser($identity); - if (!$user instanceof \Cake\Datasource\EntityInterface) { + if (!$user instanceof EntityInterface) { return false; } @@ -235,7 +235,7 @@ public function scopeView(?IdentityInterface $identity, SelectQuery $query): Sel */ protected function applyScopeConditions(?IdentityInterface $identity, SelectQuery $query, string $ability): SelectQuery { $user = $this->resolveUser($identity); - if (!$user instanceof \Cake\Datasource\EntityInterface) { + if (!$user instanceof EntityInterface) { return $query->where(['1 = 0']); } @@ -266,7 +266,7 @@ protected function applyScopeConditions(?IdentityInterface $identity, SelectQuer * @return \Cake\Datasource\EntityInterface|null */ protected function resolveUser(?IdentityInterface $identity): ?EntityInterface { - if (!$identity instanceof \Authorization\IdentityInterface) { + if (!$identity instanceof IdentityInterface) { return null; } $data = $identity->getOriginalData(); diff --git a/src/Service/FeatureService.php b/src/Service/FeatureService.php index 8c7dc99..71de493 100644 --- a/src/Service/FeatureService.php +++ b/src/Service/FeatureService.php @@ -85,13 +85,13 @@ public function getEnabledFeatures(): array { // Force disabled $result[$feature] = false; } elseif ($feature === 'roles') { - // Auto-detect - // Special handling: roles available from external source OR table - $hasExternalSource = Configure::read('TinyAuthBackend.roleSource') !== null; - $result[$feature] = $hasExternalSource || $this->tableExists($table); - } else { + // Auto-detect + // Special handling: roles available from external source OR table + $hasExternalSource = Configure::read('TinyAuthBackend.roleSource') !== null; + $result[$feature] = $hasExternalSource || $this->tableExists($table); + } else { $result[$feature] = $this->tableExists($table); - } + } } static::$cachedFeatures = $result; diff --git a/src/Service/ImportExportService.php b/src/Service/ImportExportService.php index 116213d..eabf6f4 100644 --- a/src/Service/ImportExportService.php +++ b/src/Service/ImportExportService.php @@ -185,11 +185,11 @@ public function importIni(string $content, string $mode = 'merge'): array { $prefix = null; $name = $controllerPath; - if (str_contains((string) $name, '.')) { - [$plugin, $name] = explode('.', (string) $name, 2); + if (str_contains((string)$name, '.')) { + [$plugin, $name] = explode('.', (string)$name, 2); } - if (str_contains((string) $name, '/')) { - $parts = explode('/', (string) $name); + if (str_contains((string)$name, '/')) { + $parts = explode('/', (string)$name); $name = array_pop($parts); $prefix = implode('/', $parts); } @@ -240,7 +240,7 @@ public function importIni(string $content, string $mode = 'merge'): array { } // Parse roles - $roleAliases = array_map('trim', explode(',', (string) $rolesList)); + $roleAliases = array_map('trim', explode(',', (string)$rolesList)); foreach ($roleAliases as $alias) { if (!isset($roleLookup[$alias])) { $result['errors'][] = "Unknown role: {$alias}"; diff --git a/tests/TestCase/CspComplianceTest.php b/tests/TestCase/CspComplianceTest.php index 5dea9f5..3d7c2bb 100644 --- a/tests/TestCase/CspComplianceTest.php +++ b/tests/TestCase/CspComplianceTest.php @@ -31,7 +31,7 @@ class CspComplianceTest extends TestCase { * @return void */ public function testNoAlpineDirectivesInTemplates(): void { - $offenders = $this->scanTemplates(self::ALPINE_PATTERN); + $offenders = $this->scanTemplates(static::ALPINE_PATTERN); $this->assertSame([], $offenders, "Alpine directives found:\n" . implode("\n", $offenders)); } @@ -39,7 +39,7 @@ public function testNoAlpineDirectivesInTemplates(): void { * @return void */ public function testNoInlineEventHandlersInTemplates(): void { - $offenders = $this->scanTemplates(self::INLINE_HANDLER_PATTERN); + $offenders = $this->scanTemplates(static::INLINE_HANDLER_PATTERN); $this->assertSame([], $offenders, "Inline event handlers found:\n" . implode("\n", $offenders)); } From 6f6b3527d47db82973ee12ea81f795bfa7a77b95 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Sun, 24 May 2026 20:12:57 +0200 Subject: [PATCH 03/12] Preserve forbidden gate exceptions --- src/Controller/Admin/AppController.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Controller/Admin/AppController.php b/src/Controller/Admin/AppController.php index 9f1563a..96ecdb2 100644 --- a/src/Controller/Admin/AppController.php +++ b/src/Controller/Admin/AppController.php @@ -109,6 +109,8 @@ public function beforeFilter(EventInterface $event): void { private function runGate(Closure $gate): void { try { $allowed = $gate() === true; + } catch (ForbiddenException $e) { + throw $e; } catch (Throwable $e) { // Convert any other failure (broken callable, transient DB // error in a role lookup, etc.) to a generic 403. Logging From 18bf679387c229356613c33e7f25fa941629c249 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 04:12:29 +0200 Subject: [PATCH 04/12] Restore compact() for matching view vars --- src/Controller/Admin/AclController.php | 2 +- src/Controller/Admin/AllowController.php | 2 +- src/Controller/Admin/DashboardController.php | 2 +- src/Controller/Admin/ScopesController.php | 6 +++--- src/Controller/Admin/SyncController.php | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index bc0c3ec..137661b 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -101,7 +101,7 @@ public function toggle(): ?Response { // Return updated cell HTML $this->viewBuilder()->disableAutoLayout(); - $this->set(['cell' => $cell]); + $this->set(compact('cell')); return $this->render('toggle_cell'); } diff --git a/src/Controller/Admin/AllowController.php b/src/Controller/Admin/AllowController.php index 7cf6340..6fcdc20 100644 --- a/src/Controller/Admin/AllowController.php +++ b/src/Controller/Admin/AllowController.php @@ -71,7 +71,7 @@ public function toggle(): ?Response { } $this->viewBuilder()->disableAutoLayout(); - $this->set(['action' => $action]); + $this->set(compact('action')); return $this->render('toggle_cell'); } diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index 701ee2e..f062961 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -64,7 +64,7 @@ public function concepts(): void { $featureService = new FeatureService(); $features = $featureService->getEnabledFeatures(); - $this->set(['features' => $features]); + $this->set(compact('features')); } } diff --git a/src/Controller/Admin/ScopesController.php b/src/Controller/Admin/ScopesController.php index e7184b2..d970a4a 100644 --- a/src/Controller/Admin/ScopesController.php +++ b/src/Controller/Admin/ScopesController.php @@ -17,7 +17,7 @@ public function index(): void { ->all() ->toArray(); - $this->set(['scopes' => $scopes]); + $this->set(compact('scopes')); } /** @@ -37,7 +37,7 @@ public function add(): ?Response { $this->Flash->error(__d('tinyauth_backend', 'Could not save scope.')); } - $this->set(['scope' => $scope]); + $this->set(compact('scope')); return $this->render('form'); } @@ -60,7 +60,7 @@ public function edit(int $id): ?Response { $this->Flash->error(__d('tinyauth_backend', 'Could not update scope.')); } - $this->set(['scope' => $scope]); + $this->set(compact('scope')); return $this->render('form'); } diff --git a/src/Controller/Admin/SyncController.php b/src/Controller/Admin/SyncController.php index e19f538..28b949a 100644 --- a/src/Controller/Admin/SyncController.php +++ b/src/Controller/Admin/SyncController.php @@ -54,7 +54,7 @@ public function controllers(): ?Response { $diff[] = array_merge($item, ['status' => $status]); } - $this->set(['diff' => $diff]); + $this->set(compact('diff')); return null; } @@ -94,7 +94,7 @@ public function resources(): ?Response { $diff[] = array_merge($item, ['status' => $status]); } - $this->set(['diff' => $diff]); + $this->set(compact('diff')); return null; } From cb5619ef119cd9015a57a9b43f04741834baf7bb Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 04:24:08 +0200 Subject: [PATCH 05/12] Fix review follow-up indentation --- src/Controller/Admin/AclController.php | 16 ++++++++-------- src/Controller/Admin/ResourcesController.php | 16 ++++++++-------- src/Service/FeatureService.php | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index 137661b..aa38b3e 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -79,16 +79,16 @@ public function toggle(): ?Response { $existing->type = $type; $existing->description = $description; if (!$permissionsTable->save($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to update permission'); + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to update permission'); } } else { - $permission = $permissionsTable->newEntity([ - 'action_id' => $actionId, - 'role_id' => $roleId, - 'type' => $type, - 'description' => $description, - ]); + $permission = $permissionsTable->newEntity([ + 'action_id' => $actionId, + 'role_id' => $roleId, + 'type' => $type, + 'description' => $description, + ]); if (!$permissionsTable->save($permission)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to save permission'); diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index 64be945..b8b23e4 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -117,16 +117,16 @@ public function toggle(): ?Response { $existing->type = $type; $existing->scope_id = $scopeId; if (!$resourceAclTable->save($existing)) { - $this->response = $this->response->withStatus(500); - $this->set('error', 'Failed to update permission'); + $this->response = $this->response->withStatus(500); + $this->set('error', 'Failed to update permission'); } } else { - $permission = $resourceAclTable->newEntity([ - 'resource_ability_id' => $abilityId, - 'role_id' => $roleId, - 'type' => $type, - 'scope_id' => $scopeId, - ]); + $permission = $resourceAclTable->newEntity([ + 'resource_ability_id' => $abilityId, + 'role_id' => $roleId, + 'type' => $type, + 'scope_id' => $scopeId, + ]); if (!$resourceAclTable->save($permission)) { $this->response = $this->response->withStatus(500); $this->set('error', 'Failed to create permission'); diff --git a/src/Service/FeatureService.php b/src/Service/FeatureService.php index 71de493..63cc32e 100644 --- a/src/Service/FeatureService.php +++ b/src/Service/FeatureService.php @@ -90,7 +90,7 @@ public function getEnabledFeatures(): array { $hasExternalSource = Configure::read('TinyAuthBackend.roleSource') !== null; $result[$feature] = $hasExternalSource || $this->tableExists($table); } else { - $result[$feature] = $this->tableExists($table); + $result[$feature] = $this->tableExists($table); } } From 50466ca5daa7aadae1fd788f230a3b83684bef0d Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 05:05:07 +0200 Subject: [PATCH 06/12] Restore compact() for matching view vars --- src/Controller/Admin/AclController.php | 2 +- src/Controller/Admin/AllowController.php | 2 +- src/Controller/Admin/DashboardController.php | 2 +- src/Controller/Admin/ResourcesController.php | 4 ++-- src/Controller/Admin/RolesController.php | 6 +++--- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index aa38b3e..4c2809a 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -38,7 +38,7 @@ public function index(): void { $permissions = $this->buildCellStates($roles, array_map(static fn ($action) => (int)$action->id, $actions)); } - $this->set(['tree' => $tree, 'roles' => $roles, 'selectedController' => $selectedController, 'actions' => $actions, 'permissions' => $permissions]); + $this->set(compact('tree', 'roles', 'selectedController', 'actions', 'permissions')); } /** diff --git a/src/Controller/Admin/AllowController.php b/src/Controller/Admin/AllowController.php index 6fcdc20..46721ba 100644 --- a/src/Controller/Admin/AllowController.php +++ b/src/Controller/Admin/AllowController.php @@ -37,7 +37,7 @@ public function index(): void { $controllers = $query->all()->toArray(); - $this->set(['controllers' => $controllers, 'filter' => $filter]); + $this->set(compact('controllers', 'filter')); } /** diff --git a/src/Controller/Admin/DashboardController.php b/src/Controller/Admin/DashboardController.php index f062961..1d57cb0 100644 --- a/src/Controller/Admin/DashboardController.php +++ b/src/Controller/Admin/DashboardController.php @@ -52,7 +52,7 @@ public function index(): void { ->all() ->toArray(); - $this->set(['stats' => $stats, 'features' => $features, 'recentControllers' => $recentControllers]); + $this->set(compact('stats', 'features', 'recentControllers')); } /** diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index b8b23e4..91a5cdc 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -70,7 +70,7 @@ public function index(): void { } } - $this->set(['resources' => $resources, 'roles' => $roles, 'scopes' => $scopes, 'selectedResource' => $selectedResource, 'abilities' => $abilities, 'permissions' => $permissions]); + $this->set(compact('resources', 'roles', 'scopes', 'selectedResource', 'abilities', 'permissions')); } /** @@ -139,7 +139,7 @@ public function toggle(): ?Response { $scopes = $scopesTable->find()->orderBy(['name' => 'ASC'])->toArray(); $this->viewBuilder()->disableAutoLayout(); - $this->set(['abilityId' => $abilityId, 'roleId' => $roleId, 'type' => $type, 'scope' => $scope, 'scopes' => $scopes]); + $this->set(compact('abilityId', 'roleId', 'type', 'scope', 'scopes')); return $this->render('toggle_cell'); } diff --git a/src/Controller/Admin/RolesController.php b/src/Controller/Admin/RolesController.php index c102bbb..dabe4c4 100644 --- a/src/Controller/Admin/RolesController.php +++ b/src/Controller/Admin/RolesController.php @@ -33,7 +33,7 @@ public function index(): void { $hierarchy = $rolesTable->findHierarchy(); } - $this->set(['isManaged' => $isManaged, 'hierarchy' => $hierarchy, 'roles' => $roles]); + $this->set(compact('isManaged', 'hierarchy', 'roles')); } /** @@ -64,7 +64,7 @@ public function add(): ?Response { ->orderBy(['name' => 'ASC']) ->toArray(); - $this->set(['role' => $role, 'parents' => $parents]); + $this->set(compact('role', 'parents')); return $this->render('form'); } @@ -101,7 +101,7 @@ public function edit(int $id): ?Response { ->orderBy(['name' => 'ASC']) ->toArray(); - $this->set(['role' => $role, 'parents' => $parents]); + $this->set(compact('role', 'parents')); return $this->render('form'); } From 36a87e57525de4535e31ccd66f7ce766d16302cf Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:04:13 +0200 Subject: [PATCH 07/12] Fix tinyauth-backend entity typing --- src/Controller/Admin/AclController.php | 8 ++++---- src/Controller/Admin/ResourcesController.php | 8 ++++---- src/Model/Table/RolesTable.php | 3 ++- src/Service/ImportExportService.php | 14 ++++++++------ 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index 4c2809a..9f8f998 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -65,9 +65,10 @@ public function toggle(): ?Response { $permissionsTable = $this->fetchTable('TinyAuthBackend.AclPermissions'); - $existing = $permissionsTable->find() - ->where(['action_id' => $actionId, 'role_id' => $roleId]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing = $permissionsTable->find() + ->where(['action_id' => $actionId, 'role_id' => $roleId]) + ->first(); if ($type === 'none') { if ($existing && !$permissionsTable->delete($existing)) { @@ -75,7 +76,6 @@ public function toggle(): ?Response { $this->set('error', 'Failed to delete permission'); } } elseif ($existing) { - /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ $existing->type = $type; $existing->description = $description; if (!$permissionsTable->save($existing)) { diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index 91a5cdc..5273f71 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -103,9 +103,10 @@ public function toggle(): ?Response { $resourceAclTable = $this->fetchTable('TinyAuthBackend.ResourceAcl'); - $existing = $resourceAclTable->find() - ->where(['resource_ability_id' => $abilityId, 'role_id' => $roleId]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ + $existing = $resourceAclTable->find() + ->where(['resource_ability_id' => $abilityId, 'role_id' => $roleId]) + ->first(); if ($type === 'none') { if ($existing && !$resourceAclTable->delete($existing)) { @@ -113,7 +114,6 @@ public function toggle(): ?Response { $this->set('error', 'Failed to remove permission'); } } elseif ($existing) { - /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ $existing->type = $type; $existing->scope_id = $scopeId; if (!$resourceAclTable->save($existing)) { diff --git a/src/Model/Table/RolesTable.php b/src/Model/Table/RolesTable.php index f4e42a7..c6f85fd 100644 --- a/src/Model/Table/RolesTable.php +++ b/src/Model/Table/RolesTable.php @@ -152,7 +152,8 @@ public function validationDefault(Validator $validator): Validator { return false; // Circular reference detected } $visited[] = $parentId; - $parent = $this->find()->select(['parent_id'])->where(['id' => $parentId])->first(); + /** @var \TinyAuthBackend\Model\Entity\Role|null $parent */ + $parent = $this->find()->select(['parent_id'])->where(['id' => $parentId])->first(); $parentId = $parent ? (int)$parent->parent_id : null; $maxDepth--; } diff --git a/src/Service/ImportExportService.php b/src/Service/ImportExportService.php index eabf6f4..bc1a923 100644 --- a/src/Service/ImportExportService.php +++ b/src/Service/ImportExportService.php @@ -220,9 +220,10 @@ public function importIni(string $content, string $mode = 'merge'): array { // Process actions foreach ($actions as $actionName => $rolesList) { - $action = $actionsTable->find() - ->where(['controller_id' => $controller->id, 'name' => $actionName]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ + $action = $actionsTable->find() + ->where(['controller_id' => $controller->id, 'name' => $actionName]) + ->first(); if (!$action) { $action = $actionsTable->newEntity([ @@ -250,9 +251,10 @@ public function importIni(string $content, string $mode = 'merge'): array { $roleId = $roleLookup[$alias]; - $existing = $permissionsTable->find() - ->where(['action_id' => $action->id, 'role_id' => $roleId]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing = $permissionsTable->find() + ->where(['action_id' => $action->id, 'role_id' => $roleId]) + ->first(); if (!$existing) { $permission = $permissionsTable->newEntity([ From 841e180a8193536979f5acf9c46c480eb6c643df Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:13:41 +0200 Subject: [PATCH 08/12] Fix remaining tinyauth-backend entity typing --- src/Service/ImportExportService.php | 8 ++++---- src/Service/ResourceSyncService.php | 1 + src/Service/RoleSourceService.php | 21 +++++++++++---------- src/Service/TinyAuthService.php | 1 + src/Utility/Importer.php | 13 +++++++------ 5 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/Service/ImportExportService.php b/src/Service/ImportExportService.php index bc1a923..4654488 100644 --- a/src/Service/ImportExportService.php +++ b/src/Service/ImportExportService.php @@ -220,8 +220,8 @@ public function importIni(string $content, string $mode = 'merge'): array { // Process actions foreach ($actions as $actionName => $rolesList) { - /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ - $action = $actionsTable->find() + /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ + $action = $actionsTable->find() ->where(['controller_id' => $controller->id, 'name' => $actionName]) ->first(); @@ -251,8 +251,8 @@ public function importIni(string $content, string $mode = 'merge'): array { $roleId = $roleLookup[$alias]; - /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ - $existing = $permissionsTable->find() + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing = $permissionsTable->find() ->where(['action_id' => $action->id, 'role_id' => $roleId]) ->first(); diff --git a/src/Service/ResourceSyncService.php b/src/Service/ResourceSyncService.php index 863a583..e662456 100644 --- a/src/Service/ResourceSyncService.php +++ b/src/Service/ResourceSyncService.php @@ -130,6 +130,7 @@ public function sync(array $options = []): array { if ($existing && $addDefaultAbilities) { foreach ($this->defaultAbilities as $abilityName) { + /** @var \TinyAuthBackend\Model\Entity\ResourceAbility|null $existingAbility */ $existingAbility = $abilitiesTable->find() ->where([ 'resource_id' => $existing->id, diff --git a/src/Service/RoleSourceService.php b/src/Service/RoleSourceService.php index ec71981..35c99a0 100644 --- a/src/Service/RoleSourceService.php +++ b/src/Service/RoleSourceService.php @@ -231,16 +231,17 @@ protected function syncExternalRoles(array $roles): void { } if ($role) { - $role = $rolesTable->patchEntity($role, [ - 'id' => $id, - 'alias' => $alias, - 'name' => $role->name ?: ucfirst($alias), - 'parent_id' => null, - 'sort_order' => $role->sort_order ?: $sortOrder, - ]); - $rolesTable->save($role); - - continue; + /** @var \TinyAuthBackend\Model\Entity\Role $role */ + $role = $rolesTable->patchEntity($role, [ + 'id' => $id, + 'alias' => $alias, + 'name' => $role->name ?: ucfirst($alias), + 'parent_id' => null, + 'sort_order' => $role->sort_order ?: $sortOrder, + ]); + $rolesTable->save($role); + + continue; } $role = $rolesTable->newEntity([ diff --git a/src/Service/TinyAuthService.php b/src/Service/TinyAuthService.php index 300fd75..54f0afa 100644 --- a/src/Service/TinyAuthService.php +++ b/src/Service/TinyAuthService.php @@ -271,6 +271,7 @@ public function getUserRoles(EntityInterface $user): array { if (!$multiRole) { $roleColumn = Configure::read('TinyAuthBackend.roleColumn') ?: 'role_id'; $rolesTable = TableRegistry::getTableLocator()->get('TinyAuthBackend.Roles'); + /** @var \TinyAuthBackend\Model\Entity\Role|null $role */ $role = $rolesTable->find()->where(['id' => $user->get($roleColumn)])->first(); return $role ? [$role->alias] : []; diff --git a/src/Utility/Importer.php b/src/Utility/Importer.php index cad3904..17ec20f 100644 --- a/src/Utility/Importer.php +++ b/src/Utility/Importer.php @@ -145,12 +145,13 @@ protected function ensureAction(array $row, string $actionName): array { $controllersTable->saveOrFail($controller); } - $action = $actionsTable->find() - ->where([ - 'controller_id' => $controller->id, - 'name' => $actionName, - ]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ + $action = $actionsTable->find() + ->where([ + 'controller_id' => $controller->id, + 'name' => $actionName, + ]) + ->first(); if (!$action) { $action = $actionsTable->newEntity([ From cbabe5a0b21490bd6df1c4f09e2c9df1061fc3b9 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:24:00 +0200 Subject: [PATCH 09/12] Fix tinyauth-backend cleanup typing --- src/Service/ImportExportService.php | 17 +++++++++++------ src/Service/ResourceSyncService.php | 7 +++++-- src/Service/RoleSourceService.php | 22 +++++++++++----------- src/Utility/Importer.php | 16 +++++++++------- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/src/Service/ImportExportService.php b/src/Service/ImportExportService.php index 4654488..9af7344 100644 --- a/src/Service/ImportExportService.php +++ b/src/Service/ImportExportService.php @@ -195,6 +195,7 @@ public function importIni(string $content, string $mode = 'merge'): array { } // Find or create controller + /** @var \TinyAuthBackend\Model\Entity\TinyauthController|null $controller */ $controller = $controllersTable->find() ->where([ 'plugin IS' => $plugin, @@ -217,17 +218,19 @@ public function importIni(string $content, string $mode = 'merge'): array { continue; } } + /** @var \TinyAuthBackend\Model\Entity\TinyauthController $typedController */ + $typedController = $controller; // Process actions foreach ($actions as $actionName => $rolesList) { /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ $action = $actionsTable->find() - ->where(['controller_id' => $controller->id, 'name' => $actionName]) - ->first(); + ->where(['controller_id' => $typedController->id, 'name' => $actionName]) + ->first(); if (!$action) { $action = $actionsTable->newEntity([ - 'controller_id' => $controller->id, + 'controller_id' => $typedController->id, 'name' => $actionName, 'is_public' => false, ]); @@ -239,6 +242,8 @@ public function importIni(string $content, string $mode = 'merge'): array { continue; } } + /** @var \TinyAuthBackend\Model\Entity\Action $typedAction */ + $typedAction = $action; // Parse roles $roleAliases = array_map('trim', explode(',', (string)$rolesList)); @@ -253,12 +258,12 @@ public function importIni(string $content, string $mode = 'merge'): array { /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ $existing = $permissionsTable->find() - ->where(['action_id' => $action->id, 'role_id' => $roleId]) - ->first(); + ->where(['action_id' => $typedAction->id, 'role_id' => $roleId]) + ->first(); if (!$existing) { $permission = $permissionsTable->newEntity([ - 'action_id' => $action->id, + 'action_id' => $typedAction->id, 'role_id' => $roleId, 'type' => 'allow', ]); diff --git a/src/Service/ResourceSyncService.php b/src/Service/ResourceSyncService.php index e662456..308678c 100644 --- a/src/Service/ResourceSyncService.php +++ b/src/Service/ResourceSyncService.php @@ -112,6 +112,7 @@ public function sync(array $options = []): array { $result = ['added' => 0, 'abilities_added' => 0]; foreach ($scanned as $item) { + /** @var \TinyAuthBackend\Model\Entity\Resource|null $existing */ $existing = $resourcesTable->find() ->where(['entity_class' => $item['entity_class']]) ->first(); @@ -129,18 +130,20 @@ public function sync(array $options = []): array { } if ($existing && $addDefaultAbilities) { + /** @var \TinyAuthBackend\Model\Entity\Resource $typedExisting */ + $typedExisting = $existing; foreach ($this->defaultAbilities as $abilityName) { /** @var \TinyAuthBackend\Model\Entity\ResourceAbility|null $existingAbility */ $existingAbility = $abilitiesTable->find() ->where([ - 'resource_id' => $existing->id, + 'resource_id' => $typedExisting->id, 'name' => $abilityName, ]) ->first(); if (!$existingAbility) { $ability = $abilitiesTable->newEntity([ - 'resource_id' => $existing->id, + 'resource_id' => $typedExisting->id, 'name' => $abilityName, ]); if ($abilitiesTable->save($ability)) { diff --git a/src/Service/RoleSourceService.php b/src/Service/RoleSourceService.php index 35c99a0..9e40a8b 100644 --- a/src/Service/RoleSourceService.php +++ b/src/Service/RoleSourceService.php @@ -225,23 +225,23 @@ protected function syncExternalRoles(array $roles): void { foreach ($roles as $alias => $id) { $sortOrder++; + /** @var \TinyAuthBackend\Model\Entity\Role|null $role */ $role = $rolesTable->find()->where(['id' => $id])->first(); if (!$role) { $role = $rolesTable->find()->where(['alias' => $alias])->first(); } if ($role) { - /** @var \TinyAuthBackend\Model\Entity\Role $role */ - $role = $rolesTable->patchEntity($role, [ - 'id' => $id, - 'alias' => $alias, - 'name' => $role->name ?: ucfirst($alias), - 'parent_id' => null, - 'sort_order' => $role->sort_order ?: $sortOrder, - ]); - $rolesTable->save($role); - - continue; + $role = $rolesTable->patchEntity($role, [ + 'id' => $id, + 'alias' => $alias, + 'name' => $role->name ?: ucfirst($alias), + 'parent_id' => null, + 'sort_order' => $role->sort_order ?: $sortOrder, + ]); + $rolesTable->save($role); + + continue; } $role = $rolesTable->newEntity([ diff --git a/src/Utility/Importer.php b/src/Utility/Importer.php index 17ec20f..7a04084 100644 --- a/src/Utility/Importer.php +++ b/src/Utility/Importer.php @@ -128,6 +128,7 @@ protected function ensureAction(array $row, string $actionName): array { /** @var \TinyAuthBackend\Model\Table\ActionsTable $actionsTable */ $actionsTable = $this->fetchModel('TinyAuthBackend.Actions'); + /** @var \TinyAuthBackend\Model\Entity\TinyauthController|null $controller */ $controller = $controllersTable->find() ->where([ 'plugin IS' => $row['plugin'], @@ -145,13 +146,13 @@ protected function ensureAction(array $row, string $actionName): array { $controllersTable->saveOrFail($controller); } - /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ - $action = $actionsTable->find() - ->where([ - 'controller_id' => $controller->id, - 'name' => $actionName, - ]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\Action|null $action */ + $action = $actionsTable->find() + ->where([ + 'controller_id' => $controller->id, + 'name' => $actionName, + ]) + ->first(); if (!$action) { $action = $actionsTable->newEntity([ @@ -192,6 +193,7 @@ protected function upsertAclPermission(int $actionId, string $roleAlias, string /** @var \TinyAuthBackend\Model\Table\AclPermissionsTable $permissionsTable */ $permissionsTable = $this->fetchModel('TinyAuthBackend.AclPermissions'); + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $permission */ $permission = $permissionsTable->find() ->where(['action_id' => $actionId, 'role_id' => $roleId]) ->first(); From 3cf9e8d4012a29c220ed62c93edb6dbea2f432f6 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:26:36 +0200 Subject: [PATCH 10/12] Fix tinyauth-backend review indentation --- src/Controller/Admin/AclController.php | 8 ++++---- src/Controller/Admin/ResourcesController.php | 8 ++++---- src/Model/Table/RolesTable.php | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Controller/Admin/AclController.php b/src/Controller/Admin/AclController.php index 9f8f998..188c49f 100644 --- a/src/Controller/Admin/AclController.php +++ b/src/Controller/Admin/AclController.php @@ -65,10 +65,10 @@ public function toggle(): ?Response { $permissionsTable = $this->fetchTable('TinyAuthBackend.AclPermissions'); - /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ - $existing = $permissionsTable->find() - ->where(['action_id' => $actionId, 'role_id' => $roleId]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */ + $existing = $permissionsTable->find() + ->where(['action_id' => $actionId, 'role_id' => $roleId]) + ->first(); if ($type === 'none') { if ($existing && !$permissionsTable->delete($existing)) { diff --git a/src/Controller/Admin/ResourcesController.php b/src/Controller/Admin/ResourcesController.php index 5273f71..215e4e5 100644 --- a/src/Controller/Admin/ResourcesController.php +++ b/src/Controller/Admin/ResourcesController.php @@ -103,10 +103,10 @@ public function toggle(): ?Response { $resourceAclTable = $this->fetchTable('TinyAuthBackend.ResourceAcl'); - /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ - $existing = $resourceAclTable->find() - ->where(['resource_ability_id' => $abilityId, 'role_id' => $roleId]) - ->first(); + /** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */ + $existing = $resourceAclTable->find() + ->where(['resource_ability_id' => $abilityId, 'role_id' => $roleId]) + ->first(); if ($type === 'none') { if ($existing && !$resourceAclTable->delete($existing)) { diff --git a/src/Model/Table/RolesTable.php b/src/Model/Table/RolesTable.php index c6f85fd..418771f 100644 --- a/src/Model/Table/RolesTable.php +++ b/src/Model/Table/RolesTable.php @@ -152,8 +152,8 @@ public function validationDefault(Validator $validator): Validator { return false; // Circular reference detected } $visited[] = $parentId; - /** @var \TinyAuthBackend\Model\Entity\Role|null $parent */ - $parent = $this->find()->select(['parent_id'])->where(['id' => $parentId])->first(); + /** @var \TinyAuthBackend\Model\Entity\Role|null $parent */ + $parent = $this->find()->select(['parent_id'])->where(['id' => $parentId])->first(); $parentId = $parent ? (int)$parent->parent_id : null; $maxDepth--; } From d6ed62bb95e63949a85c9e5ff015680a836f1998 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:32:41 +0200 Subject: [PATCH 11/12] Fix role source cleanup typing --- src/Service/RoleSourceService.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Service/RoleSourceService.php b/src/Service/RoleSourceService.php index 9e40a8b..5c6cc00 100644 --- a/src/Service/RoleSourceService.php +++ b/src/Service/RoleSourceService.php @@ -232,14 +232,15 @@ protected function syncExternalRoles(array $roles): void { } if ($role) { - $role = $rolesTable->patchEntity($role, [ + /** @var \TinyAuthBackend\Model\Entity\Role $updatedRole */ + $updatedRole = $rolesTable->patchEntity($role, [ 'id' => $id, 'alias' => $alias, 'name' => $role->name ?: ucfirst($alias), 'parent_id' => null, 'sort_order' => $role->sort_order ?: $sortOrder, ]); - $rolesTable->save($role); + $rolesTable->save($updatedRole); continue; } From 32ad96f5bc217519a851249f88d759d485a73da3 Mon Sep 17 00:00:00 2001 From: Mark Scherer Date: Mon, 25 May 2026 14:42:31 +0200 Subject: [PATCH 12/12] Fix role source entity access --- src/Service/RoleSourceService.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Service/RoleSourceService.php b/src/Service/RoleSourceService.php index 5c6cc00..749881c 100644 --- a/src/Service/RoleSourceService.php +++ b/src/Service/RoleSourceService.php @@ -232,13 +232,15 @@ protected function syncExternalRoles(array $roles): void { } if ($role) { + /** @var \TinyAuthBackend\Model\Entity\Role $typedRole */ + $typedRole = $role; /** @var \TinyAuthBackend\Model\Entity\Role $updatedRole */ - $updatedRole = $rolesTable->patchEntity($role, [ + $updatedRole = $rolesTable->patchEntity($typedRole, [ 'id' => $id, 'alias' => $alias, - 'name' => $role->name ?: ucfirst($alias), + 'name' => $typedRole->name ?: ucfirst($alias), 'parent_id' => null, - 'sort_order' => $role->sort_order ?: $sortOrder, + 'sort_order' => $typedRole->sort_order ?: $sortOrder, ]); $rolesTable->save($updatedRole);