Skip to content
3 changes: 1 addition & 2 deletions src/Auth/AclAdapter/DbAclAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,8 @@ protected function buildKey(?string $plugin, ?string $prefix, string $controller
if ($prefix) {
$key .= $prefix . '/';
}
$key .= $controller;

return $key;
return $key . $controller;
}

}
3 changes: 1 addition & 2 deletions src/Auth/AllowAdapter/DbAllowAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ protected function buildKey(?string $plugin, ?string $prefix, string $controller
if ($prefix) {
$key .= $prefix . '/';
}
$key .= $controller;

return $key;
return $key . $controller;
}

}
50 changes: 23 additions & 27 deletions src/Controller/Admin/AclController.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,39 @@ 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');
}

$permissionsTable = $this->fetchTable('TinyAuthBackend.AclPermissions');

/** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */
$existing = $permissionsTable->find()
->where(['action_id' => $actionId, 'role_id' => $roleId])
->first();
Comment thread
dereuromark marked this conversation as resolved.

if ($type === 'none') {
if ($existing) {
if (!$permissionsTable->delete($existing)) {
$this->response = $this->response->withStatus(500);
$this->set('error', 'Failed to delete permission');
}
if ($existing && !$permissionsTable->delete($existing)) {
$this->response = $this->response->withStatus(500);
$this->set('error', 'Failed to delete permission');
}
} elseif ($existing) {
$existing->type = $type;
$existing->description = $description;
if (!$permissionsTable->save($existing)) {
$this->response = $this->response->withStatus(500);
$this->set('error', 'Failed to update permission');
}
Comment thread
dereuromark marked this conversation as resolved.
} else {
/** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */
if ($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 {
$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');
}
$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');
}
}

Expand All @@ -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) {
Expand Down Expand Up @@ -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;
}
Expand Down
1 change: 0 additions & 1 deletion src/Controller/Admin/AppController.php
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ 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
Expand Down
46 changes: 21 additions & 25 deletions src/Controller/Admin/ResourcesController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

Expand All @@ -103,37 +103,33 @@ 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();
Comment thread
dereuromark marked this conversation as resolved.

if ($type === 'none') {
if ($existing) {
if (!$resourceAclTable->delete($existing)) {
$this->response = $this->response->withStatus(500);
$this->set('error', 'Failed to remove permission');
}
if ($existing && !$resourceAclTable->delete($existing)) {
$this->response = $this->response->withStatus(500);
$this->set('error', 'Failed to remove permission');
}
} elseif ($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');
}
Comment thread
dereuromark marked this conversation as resolved.
} else {
/** @var \TinyAuthBackend\Model\Entity\ResourceAcl|null $existing */
if ($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 {
$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');
}
$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');
}
}

Expand Down
4 changes: 2 additions & 2 deletions src/Identity/EntityIdentity.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 AuthorizationServiceInterface) {
throw new BadMethodCallException(
'EntityIdentity::canResult() requires an AuthorizationService. '
. 'Construct EntityIdentity with a service, or call can() instead.',
Expand All @@ -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 AuthorizationServiceInterface) {
return $resource;
}

Expand Down
8 changes: 3 additions & 5 deletions src/Model/Table/RolesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -120,12 +120,9 @@ 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.'),
])
Expand Down Expand Up @@ -155,6 +152,7 @@ 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();
$parentId = $parent ? (int)$parent->parent_id : null;
Comment thread
dereuromark marked this conversation as resolved.
$maxDepth--;
Expand Down
4 changes: 2 additions & 2 deletions src/Model/Table/ScopesTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.'),
]);

Expand All @@ -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.'),
]);

Expand Down
8 changes: 4 additions & 4 deletions src/Policy/TinyAuthPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 EntityInterface) {
return false;
}

Expand All @@ -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 EntityInterface) {
return false;
}

Expand Down Expand Up @@ -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 EntityInterface) {
return $query->where(['1 = 0']);
}

Expand Down Expand Up @@ -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 IdentityInterface) {
return null;
}
$data = $identity->getOriginalData();
Expand Down
2 changes: 1 addition & 1 deletion src/Policy/TinyAuthResolver.php
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down
16 changes: 7 additions & 9 deletions src/Service/FeatureService.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,15 +84,13 @@ public function getEnabledFeatures(): array {
} elseif ($configValue === false) {
// Force disabled
$result[$feature] = false;
} else {
} elseif ($feature === 'roles') {
// 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 {
$result[$feature] = $this->tableExists($table);
}
// 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);
}
}

Expand Down Expand Up @@ -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;
Expand Down
25 changes: 16 additions & 9 deletions src/Service/ImportExportService.php
Original file line number Diff line number Diff line change
Expand Up @@ -185,16 +185,17 @@ 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);
}

// Find or create controller
/** @var \TinyAuthBackend\Model\Entity\TinyauthController|null $controller */
$controller = $controllersTable->find()
->where([
'plugin IS' => $plugin,
Expand All @@ -217,16 +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])
->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,
]);
Expand All @@ -238,9 +242,11 @@ 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(',', $rolesList));
$roleAliases = array_map('trim', explode(',', (string)$rolesList));
foreach ($roleAliases as $alias) {
if (!isset($roleLookup[$alias])) {
$result['errors'][] = "Unknown role: {$alias}";
Expand All @@ -250,13 +256,14 @@ public function importIni(string $content, string $mode = 'merge'): array {

$roleId = $roleLookup[$alias];

/** @var \TinyAuthBackend\Model\Entity\AclPermission|null $existing */
$existing = $permissionsTable->find()
->where(['action_id' => $action->id, 'role_id' => $roleId])
->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',
]);
Expand Down
Loading
Loading