passbolt/passbolt_api · error · SubscriptionException

The file could not be read.

Error message

The file {0} could not be read.

What it means

SubscriptionKeyImportService::importFromFile() calls is_readable($fileName) after confirming the file exists; if the filesystem denies read access it throws SubscriptionException 'The file {0} could not be read.'

Solutions

  1. Chown/chmod the file so the web/CLI PHP user can read it: chown www-data file && chmod 640 file
  2. Check the directory's execute permission too (the PHP user must be able to traverse to the file)
  3. Inspect SELinux/AppArmor denials in audit logs if permissions look correct

Example fix

// shell before
-rw------- root root /etc/passbolt/subscription.txt
// shell after
-rw-r----- www-data www-data /etc/passbolt/subscription.txt
Defensive patterns

Strategy: validation

Validate before calling

if (!is_readable($path)) { throw new RuntimeException("Cannot read $path as user " . get_current_user()); }

Type guard

null

Try / catch

try { $svc->importFromFile($path, $uac); } catch (SubscriptionException $e) { // fix file ownership/permissions }

Prevention

When it happens

Trigger: Import invoked on an existing license file whose OS permissions, owner, or SELinux/AppArmor context prevent the PHP process user from reading it.

Common situations: License file uploaded by root or owned by another user (e.g. 0600 root:root) while PHP runs as www-data; restrictive umask after manual copy; Docker volume mounted with wrong ownership.

Understand the failure class

Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltEe/Subscription/src/Service/Subscriptions/SubscriptionKeyImportService.php:40

/**
 * Persists a subscription key supplied via file or text.
 */
class SubscriptionKeyImportService
{
    /**
     * @param string $fileName Path to a file containing the subscription key.
     * @param \App\Utility\UserAccessControl $uac UAC object.
     * @return \Passbolt\Subscription\Model\Dto\SubscriptionKeyDto
     * @throws \Passbolt\Subscription\Error\Exception\Subscriptions\SubscriptionException If the file or the contained subscription is not valid.
     */
    public function importFromFile(string $fileName, UserAccessControl $uac): SubscriptionKeyDto
    {
        if (!file_exists($fileName)) {
            throw new SubscriptionException(__('The file {0} could not be found.', $fileName));
        }
        if (!is_readable($fileName)) {
            throw new SubscriptionException(__('The file {0} could not be read.', $fileName));
        }
        $subscription = file_get_contents($fileName);
        if (!$subscription) {
            throw new SubscriptionException(__('The file {0} could not be read.', $fileName));
        }

        return $this->import($subscription, $uac);
    }

    /**
     * @param string|null $subscription Raw subscription key payload (Base64).
     * @param \App\Utility\UserAccessControl $uac UAC object.
     * @return \Passbolt\Subscription\Model\Dto\SubscriptionKeyDto
     * @throws \Passbolt\Subscription\Error\Exception\Subscriptions\SubscriptionException If the subscription is not valid.
     */
    public function import(?string $subscription, UserAccessControl $uac): SubscriptionKeyDto
    {
        return (new SubscriptionKeySaveService())->save($subscription, $uac);

View on GitHub (pinned to 31c1bbc10f)