passbolt/passbolt_api · error · InternalErrorException

No permission found for resource ID

Error message

No permission found for resource ID {0}

What it means

Thrown in migrate() when a V4 resource has zero associated permissions. Personal-resource metadata encryption needs at least an owner permission to determine the encrypting user key; a permissionless resource cannot be migrated. The resource is skipped and the error recorded in the migration result.

Solutions

  1. Assign a valid owner permission to the resource for an existing active user (insert into permissions with type OWNER/15), then re-run migration.
  2. If the resource is unwanted orphan data, delete it, then re-run migration.
  3. Audit V4 resources without permissions via a SQL query joining permissions before running migration.
  4. Re-run migration; only flagged resources fail, others migrate.

Example fix

// before: orphaned resource with no permissions
// after: give it an owner
INSERT INTO permissions (id, aco, aco_foreign_key, aro, aro_foreign_key, type, created, modified)
VALUES (UUID(), 'App.Model.Resource', '<resource-id>', 'User', '<active-user-id>', 15, NOW(), NOW());
Defensive patterns

Strategy: validation

Validate before calling

// before migrating, find V4 resources without permissions
$orphans = $resourcesTable->find('all')
    ->leftJoinWith('Permissions')
    ->where(['resource_type_id IN' => $v4TypeIds, 'Permissions.id IS' => null])
    ->all();

Type guard

if ($resource->permissions === null || count($resource->permissions) === 0) { skip($resource); }

Prevention

When it happens

Trigger: Running migrate() when the resources table contains a V4 resource (matching one of the four V4 resource type IDs) whose permissions set is empty — e.g. its sole owner was hard-deleted along with the permission, or permissions rows were removed manually or by a cleanup script.

Common situations: Legacy instances where users were deleted without reassigning their resources; database restores that dropped permission rows; orphaned resources left by earlier passbolt versions' cleanup bugs.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/c45e32ce975f89c3. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Service/Migration/MigrateAllV4ResourcesToV5Service.php:112

            ->toArray();

        if (empty($resources)) {
            $this->addError(['error_message' => __('No resources to migrate.')]);

            return $this->getResult();
        }

        foreach ($resources as $resource) {
            $dto = MetadataResourceDto::fromArray($resource->toArray());

            try {
                if ($dto->isV5()) {
                    $msg = __('Resource ID "{0}" is already V5', $resource->id);
                    throw new InternalErrorException($msg);
                }
                if (count($resource->permissions) === 0) {
                    $msg = __('No permission found for resource ID {0}', $resource->id);
                    throw new InternalErrorException($msg);
                }
                if (count($resource->permissions) === 1 && !$resource->permissions[0]->isAroGroup()) {
                    $this->migratePersonal($dto, $resource);
                } else {
                    $this->migrateShared($dto, $resource);
                }
                $this->addMigrated($resource);
            } catch (Exception $e) {
                // Continue with next resource if any error
                $error = ['resource_id' => $resource->id, 'error_message' => $e->getMessage()];
                if (Configure::read('debug')) {
                    $error['trace'] = $e->getTraceAsString();
                }
                $this->addError($error);
            }
        }

        return $this->getResult();

View on GitHub (pinned to 31c1bbc10f)