Problem/Motivation

Users should be able to specify an env var containing the JSON auth config instead of a file name.

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

ptmkenny created an issue. See original summary.

ptmkenny’s picture

Claude plan:

 Allow env-var-supplied credentials in firebase_php

 Context

 The firebase_php module currently stores credentials only as a file path in
 firebase_php.settings:credentials_path. The form requires the path to point at
 an existing readable file, and the service hands the path string straight to
 Kreait\Firebase\Factory::withServiceAccount().

 You want the same flexibility used by store_webhooks_google: at runtime, the
 config value can hold either a file path or the raw service-account JSON,
 so a deployment can populate the value from an environment variable via a
 settings.php config override (e.g.
 $config['firebase_php.settings']['credentials'] = getenv('EDO_FIREBASE_SERVICE_ACCOUNT_JSON');)
 without any file existing on disk. Kreait\Firebase\Factory::withServiceAccount()
 already accepts either a file path string or a decoded credentials array, so
 the runtime branch is symmetric with store_webhooks_google's getClient().

 Important security stance: The admin form must reject pasted JSON — JSON
 credentials must never be persisted in the config table. The form accepts a
 file path; the env-var route is reserved for settings.php overrides, which
 inject JSON at runtime without touching persistent storage.

 You also asked to rename the config key from credentials_path to
 credentials (since it's no longer just a path).

 Approach

 1. Rename the config key everywhere

 Schema — /workspace/private/dev/firebase_php/config/schema/firebase_php.schema.yml
 - Replace the credentials_path mapping (currently type: string, label
 'Firebase credentials path') with:
 credentials:
   type: string
   label: 'Firebase credentials (file path or JSON via settings.php override)'
 - Keep type: string (not text) — the form only stores a path, so multi-line
 is unnecessary. The runtime can still receive JSON content via override at
 request time; that JSON does not flow through schema validation since it
 isn't saved.

 Default install config — /workspace/private/dev/firebase_php/config/install/firebase_php.settings.yml
 - Rename credentials_path: '' to credentials: ''.

 Settings enum — /workspace/private/dev/firebase_php/src/Enum/FirebasePhpSettings.php:11
 - Replace case CredentialsPath = 'credentials_path'; with
 case Credentials = 'credentials';. Update the three call-sites in the form
 and one in the service.

 Exported site config — /workspace/private/config/base_edoai/firebase_php.settings.yml:3
 - Rename the credentials_path: key to credentials: (value unchanged) so a
 fresh drush cim against this site continues to work after the rename. The
 existing value (a private:// path) is still valid.

 Update hook — create /workspace/private/dev/firebase_php/firebase_php.install (new file)
 - Add firebase_php_update_11001():
   - Load editable firebase_php.settings.
   - If credentials_path is set and credentials is empty, copy the value.
   - Clear credentials_path.
   - Save.
   - Return a t-string describing the migration.

 This is the only new file the change requires.

 2. Form: textarea, file-path-only, JSON rejection

 Form — /workspace/private/dev/firebase_php/src/Form/FirebasePhpConfigurationForm.php

 - Field (lines 48-59): keep '#type' => 'textarea' (per your earlier
 preference) with '#rows' => 3, change the title from 'Credentials Path'
 to 'Credentials', and update the description to:

 ▎ The path to the JSON credentials file. SECURITY WARNING: this file MUST be
 ▎ outside the Drupal webroot. Path may be absolute (e.g. /etc/foobar.json),
 ▎ relative to the Drupal directory (e.g. ../firebase/foobar.json), or a
 ▎ stream wrapper (e.g. private://firebase/foobar.json). To populate from
 ▎ an environment variable instead, leave this field blank and set
 ▎ $config['firebase_php.settings']['credentials'] = getenv('YOUR_VAR');
 ▎ in settings.php — do NOT paste the JSON contents into this field, as
 ▎ config values are persisted to the database.

 - Update the FirebasePhpSettings::CredentialsPath->value references on
 lines 48, 57, 88 to FirebasePhpSettings::Credentials->value.
 - Submit (line 88): just the enum reference change above. The submitted
 value is trimmed before storage so trailing whitespace doesn't break path
 checks.
 - Validate (lines 100-122) — replace the body:
   a. Read the trimmed value from FirebasePhpSettings::Credentials->value.
   b. JSON rejection first. If json_decode($value, TRUE) returns an array
 (i.e. the user pasted credentials JSON), call setErrorByName on the
 credentials field with a message:
   ▎ Do not paste the credentials JSON into this field — config is stored in
 ▎ the database. Provide a file path instead, or set the value via a
 ▎ getenv() override in settings.php.
 ▎ Return early.
   c. Run the existing checks (is_file, is_readable, reject public://).
 Fix str_starts_with('public://', $file_path) (arguments reversed in
 the current code, so the public:// check never triggers) →
 str_starts_with($file_path, 'public://').
   d. Use a stable error key — pass FirebasePhpSettings::Credentials->value to
 setErrorByName so errors highlight the actual field, instead of the
 current inconsistent 'file_location' / 'path_invalid'.

 3. Runtime: decode JSON, else treat as path

 Service — /workspace/private/dev/firebase_php/src/Service/FirebasePhpMessagingService.php:47-55

 The runtime still has to handle a JSON string, because the value can arrive at
 runtime via a settings.php override populated from getenv(). That JSON
 never passes through the form, so this is the only place it's decoded.

 Replace the current block:
 $credentials = $config->get(FirebasePhpSettings::CredentialsPath->value);
 if ($credentials === NULL) {
   throw new FirebasePhpCredentialsNotFoundException(
     "Failed to get Firebase credentials!",
   );
 }

 $factory = (new Factory)->withServiceAccount($credentials);

 with:
 $credentials = $config->get(FirebasePhpSettings::Credentials->value);
 if (!is_string($credentials) || $credentials === '') {
   throw new FirebasePhpCredentialsNotFoundException(
     'Failed to get Firebase credentials!',
   );
 }

 $decoded = json_decode($credentials, TRUE);
 if (is_array($decoded)) {
   $serviceAccount = $decoded;
 }
 elseif (is_file($credentials)) {
   $serviceAccount = $credentials;
 }
 else {
   throw new FirebasePhpCredentialsNotFoundException(
     'Firebase credentials value is neither valid JSON (via settings.php override) nor an existing file path.',
   );
 }

 $factory = (new Factory)->withServiceAccount($serviceAccount);

 Note: the empty-string guard is added because $config->get() returns the
 install default '' rather than NULL for unset values — the previous
 === NULL check was already incorrect; this fixes it as a side effect.

 4. Files modified summary

 ┌──────────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────┐
 │                             File                             │                       Change                        │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/config/schema/firebase_php.schema.yml       │ rename key, update label                            │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/config/install/firebase_php.settings.yml    │ rename key                                          │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/src/Enum/FirebasePhpSettings.php            │ CredentialsPath → Credentials                       │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/src/Service/FirebasePhpMessagingService.php │ JSON-or-path branch; fix NULL/empty guard           │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/src/Form/FirebasePhpConfigurationForm.php   │ textarea, new description, JSON rejection, fix      │
 │                                                              │ str_starts_with arg order, fix error key            │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ dev/firebase_php/firebase_php.install (new)                  │ firebase_php_update_11001() migrates                │
 │                                                              │ credentials_path → credentials                      │
 ├──────────────────────────────────────────────────────────────┼─────────────────────────────────────────────────────┤
 │ config/base_edoai/firebase_php.settings.yml                  │ rename key in exported site config                  │
 └──────────────────────────────────────────────────────────────┴─────────────────────────────────────────────────────┘

 No changes needed to:
 - firebase_php.services.yml (DI shape unchanged)
 - firebase_php.routing.yml, firebase_php.info.yml
 - The kernel install test (only exercises install/uninstall lifecycle)
 - settings.edo.php (no firebase credential override is currently wired —
 add later if/when you switch to env-var delivery)

 Verification

 1. Static checks in /workspace/private:
   - composer code-kunkun (phpcs Drupal/DrupalPractice — the composer script
 already targets web/modules/dev/firebase_php).
   - composer phpstan.
 2. Kernel test (existing) —
 ddev exec phpunit web/modules/dev/firebase_php/tests/src/Kernel/FirebasePhpInstallTest.php
 confirms install/uninstall still works after the schema rename.
 3. Update path on the local ddev site:
   - ddev drush cr
   - ddev drush updb -y — should run firebase_php_update_11001() and
 migrate the existing credentials_path value.
   - ddev drush cget firebase_php.settings credentials should print the
 migrated path; credentials_path should no longer appear.
 4. Form behaviour at https://edoai.ddev.site/admin/config/services/firebase_php:
   - Field renders as a textarea labelled "Credentials".
   - Saving the existing private:// path passes validation.
   - Saving an arbitrary string that is neither a path nor JSON fails with
 the existing "no file at the specified location" error, attached to the
 credentials field.
   - Pasting valid JSON fails validation with the "do not paste JSON"
 error — JSON never reaches the database.
   - A public://... path fails with the "insecure" error (now actually
 reachable).
 5. Runtime, file-path branch: with the existing private://... path
 saved, exercise FirebasePhpSendTestMessageForm to confirm the path
 branch still works end-to-end.
 6. Runtime, env-var/JSON branch: temporarily add to
 web/sites/default/settings.edo.php:
 $config['firebase_php.settings']['credentials'] =
 file_get_contents('/var/www/html/private/edosenseidotcom-edoai-firebase-adminsdk-9ztyv-26ab3a2a19.json');
 6. (or use getenv() once an env var is provisioned), ddev drush cr, and
 re-run the test message form. Confirms the JSON branch decodes and feeds
 the Firebase factory correctly. Remove the override afterwards.

  • ptmkenny committed e3793819 on 8.0.x
    feat: #3586810 Enable firebase auth to be configured by env var, not...
ptmkenny’s picture

Status: Active » Fixed

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.