From 25f3214a48a02993035b04867f44a46e289deb72 Mon Sep 17 00:00:00 2001 From: Clem Blanco Date: Thu, 5 Oct 2023 17:48:58 +0200 Subject: [PATCH] Support configuration of credentials with a config array (#202) --- README.md | 41 ++++++++++++++++---- config/firebase.php | 8 +--- src/FirebaseProjectManager.php | 13 ++++--- tests/FirebaseProjectManagerTest.php | 57 ++++++++++++++++++++++++++-- tests/ServiceProviderTest.php | 2 +- 5 files changed, 97 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index a9754f0..45279ee 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,8 @@ Please read about the future of the Firebase Admin PHP SDK on the - [Installation](#installation) - [Laravel](#laravel) - [Configuration](#configuration) + - [Credentials with JSON files](#credentials-with-json-files) + - [Credentials with Arrays](#credentials-with-arrays) - [Usage](#usage) - [Multiple projects](#multiple-projects) - [Supported Versions](#supported-versions) @@ -37,12 +39,6 @@ composer require kreait/laravel-firebase In order to access a Firebase project and its related services using a server SDK, requests must be authenticated. For server-to-server communication this is done with a Service Account. -The package uses auto discovery for the default project to find the credentials needed for authenticating requests to -the Firebase APIs by inspecting certain environment variables and looking into Google's well known path(s). - -If you don't want a service account to be auto-discovered, provide it by setting the `GOOGLE_APPLICATION_CREDENTIALS` -environment variable or by adapting the package configuration. - If you don't already have generated a Service Account, you can do so by following the instructions from the official documentation pages at https://firebase.google.com/docs/admin/setup#initialize_the_sdk. @@ -64,6 +60,37 @@ by copying it to your local `config` directory or by defining the environment va php artisan vendor:publish --provider="Kreait\Laravel\Firebase\ServiceProvider" --tag=config ``` +### Credentials with JSON files + +The package uses auto discovery for the default project to find the credentials needed for authenticating requests to +the Firebase APIs by inspecting certain environment variables and looking into Google's well known path(s). + +If you don't want a service account to be auto-discovered, provide it by setting the `FIREBASE_CREDENTIALS` or `GOOGLE_APPLICATION_CREDENTIALS` environment variable or by adapting the package configuration, like so for example: + +```.env +FIREBASE_CREDENTIALS=storage/app/firebase-auth.json +``` + +### Credentials with Arrays + +If you prefer to have more control over the configuration items required to configure the credentials, you can also transpose the Service Account JSON file as an array within your `config/firebase.php` file. + +```php +'credentials' => [ + 'type' => 'service_account', + 'project_id' => 'some-project-123', + 'private_key_id' => '123456789', + 'private_key' => '-----BEGIN PRIVATE KEY-----\nFOO_BAR_123456789\n-----END PRIVATE KEY-----\n', + 'client_email' => 'firebase-adminsdk-cwiuo@some-project-123.iam.gserviceaccount.com', + 'client_id' => '123456789', + 'auth_uri' => 'https://accounts.google.com/o/oauth2/auth', + 'token_uri' => 'https://oauth2.googleapis.com/token', + 'auth_provider_x509_cert_url' => 'https://www.googleapis.com/oauth2/v1/certs', + 'client_x509_cert_url' => 'https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-cwiuo%40some-project-123.iam.gserviceaccount.com', + 'universe_domain' => 'googleapis.com', +], +``` + ## Usage Once you have retrieved a component, please refer to the [documentation of the Firebase PHP Admin SDK](https://firebase-php.readthedocs.io) @@ -96,7 +123,7 @@ Earlier versions will receive security fixes as long as their **lowest** SDK req can find the currently supported versions and support options in the [SDK's README](https://github.com/kreait/firebase-php). | Version | Initial Release | Supported SDK Versions | Supported Laravel Versions | Status | -|---------|-----------------|------------------------|----------------------------|-------------| +| ------- | --------------- | ---------------------- | -------------------------- | ----------- | | `5.x` | 13 Jan 2023 | `^7.0` | `^9.0` | Active | | `4.x` | 09 Jan 2022 | `^6.0` | `^8.0` | End of life | | `3.x` | 01 Nov 2020 | `^5.24` | `^6.0, ^7.0, ^8.0` | End of life | diff --git a/config/firebase.php b/config/firebase.php index 1b541a1..10118ee 100644 --- a/config/firebase.php +++ b/config/firebase.php @@ -46,9 +46,7 @@ * first time you try to access a component of the Firebase Admin SDK. * */ - 'credentials' => [ - 'file' => env('FIREBASE_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')), - ], + 'credentials' => env('FIREBASE_CREDENTIALS', env('GOOGLE_APPLICATION_CREDENTIALS')), /* * ------------------------------------------------------------------------ @@ -180,9 +178,7 @@ */ 'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'), - 'guzzle_middlewares' => [ - - ] + 'guzzle_middlewares' => [], ], ], ], diff --git a/src/FirebaseProjectManager.php b/src/FirebaseProjectManager.php index 12221d5..3a0dbe8 100644 --- a/src/FirebaseProjectManager.php +++ b/src/FirebaseProjectManager.php @@ -47,7 +47,7 @@ protected function configuration(string $name): array return $config; } - protected function resolveCredentials(string $credentials): string + protected function resolveJsonCredentials(string $credentials): string { $isJsonString = \str_starts_with($credentials, '{'); $isAbsoluteLinuxPath = \str_starts_with($credentials, '/'); @@ -68,10 +68,12 @@ protected function configure(string $name): FirebaseProject $factory = $factory->withTenantId($tenantId); } - if ($credentials = $config['credentials']['file'] ?? null) { - $resolvedCredentials = $this->resolveCredentials((string) $credentials); + if ($credentials = $config['credentials']['file'] ?? ($config['credentials'] ?? null)) { + if (is_string($credentials)) { + $credentials = $this->resolveJsonCredentials($credentials); + } - $factory = $factory->withServiceAccount($resolvedCredentials); + $factory = $factory->withServiceAccount($credentials); } if ($databaseUrl = $config['database']['url'] ?? null) { @@ -97,8 +99,7 @@ protected function configure(string $name): FirebaseProject $factory = $factory ->withVerifierCache($cache) - ->withAuthTokenCache($cache) - ; + ->withAuthTokenCache($cache); } if ($logChannel = $config['logging']['http_log_channel'] ?? null) { diff --git a/tests/FirebaseProjectManagerTest.php b/tests/FirebaseProjectManagerTest.php index adfd87f..9e0d07d 100644 --- a/tests/FirebaseProjectManagerTest.php +++ b/tests/FirebaseProjectManagerTest.php @@ -19,7 +19,7 @@ final class FirebaseProjectManagerTest extends TestCase { protected function defineEnvironment($app): void { - $app['config']->set('firebase.projects.app.credentials.file', __DIR__ . '/_fixtures/service_account.json'); + $app['config']->set('firebase.projects.app.credentials', __DIR__ . '/_fixtures/service_account.json'); } /** @@ -66,7 +66,28 @@ public function calls_are_passed_to_default_project(): void /** * @test */ - public function credentials_can_be_configured(): void + public function credentials_can_be_configured_using_a_json_file(): void + { + // Reference credentials + $credentialsPath = \realpath(__DIR__ . '/_fixtures/service_account.json'); + $credentials = \json_decode(\file_get_contents($credentialsPath), true); + + // Set configuration and retrieve project + $projectName = 'app'; + $this->app->config->set('firebase.projects.' . $projectName . '.credentials', \realpath(__DIR__ . '/_fixtures/service_account.json')); + $factory = $this->factoryForProject($projectName); + + // Retrieve service account + $serviceAccount = $this->getAccessibleProperty($factory, 'serviceAccount')->getValue($factory); + + // Validate value + $this->assertSame($credentials, $serviceAccount); + } + + /** + * @test + */ + public function json_file_credentials_can_be_used_using_the_deprecated_configuration_entry(): void { // Reference credentials $credentialsPath = \realpath(__DIR__ . '/_fixtures/service_account.json'); @@ -84,6 +105,34 @@ public function credentials_can_be_configured(): void $this->assertSame($credentials, $serviceAccount); } + /** + * @test + */ + public function credentials_can_be_configured_using_an_array(): void + { + // Set configuration and retrieve project + $projectName = 'app'; + $this->app->config->set('firebase.projects.' . $projectName . '.credentials', $credentials = [ + 'type' => 'service_account', + 'project_id' => 'project', + 'private_key_id' => 'private_key_id', + 'private_key' => '-----BEGIN PRIVATE KEY-----\nsome gibberish\n-----END PRIVATE KEY-----\n', + 'client_email' => 'client@email.tld', + 'client_id' => '1234567890', + 'auth_uri' => 'https://some.google.tld/o/oauth2/auth', + 'token_uri' => 'https://some.google.tld/o/oauth2/token', + 'auth_provider_x509_cert_url' => 'https://some.google.tld/oauth2/v1/certs', + 'client_x509_cert_url' => 'https://some.google.tld/robot/v1/metadata/x509/user%40project.iam.gserviceaccount.com', + ]); + $factory = $this->factoryForProject($projectName); + + // Retrieve service account + $serviceAccount = $this->getAccessibleProperty($factory, 'serviceAccount')->getValue($factory); + + // Validate value + $this->assertSame($credentials, $serviceAccount); + } + /** * @test */ @@ -101,8 +150,8 @@ public function projects_can_have_different_credentials(): void $secondProjectName = 'another-app'; // Set service accounts explicitly - $this->app->config->set('firebase.projects.' . $projectName . '.credentials.file', \realpath(__DIR__ . '/_fixtures/service_account.json')); - $this->app->config->set('firebase.projects.' . $secondProjectName . '.credentials.file', \realpath(__DIR__ . '/_fixtures/another_service_account.json')); + $this->app->config->set('firebase.projects.' . $projectName . '.credentials', \realpath(__DIR__ . '/_fixtures/service_account.json')); + $this->app->config->set('firebase.projects.' . $secondProjectName . '.credentials', \realpath(__DIR__ . '/_fixtures/another_service_account.json')); // Retrieve factories and service accounts $factory = $this->factoryForProject($projectName); diff --git a/tests/ServiceProviderTest.php b/tests/ServiceProviderTest.php index a47b2ef..d679e7b 100644 --- a/tests/ServiceProviderTest.php +++ b/tests/ServiceProviderTest.php @@ -16,7 +16,7 @@ final class ServiceProviderTest extends TestCase */ public function it_provides_components(): void { - $this->app->config->set('firebase.projects.app.credentials.file', \realpath(__DIR__ . '/_fixtures/service_account.json')); + $this->app->config->set('firebase.projects.app.credentials', \realpath(__DIR__ . '/_fixtures/service_account.json')); $this->assertInstanceOf(Firebase\Contract\AppCheck::class, $this->app->make(Firebase\Contract\AppCheck::class)); $this->assertInstanceOf(Firebase\Contract\Auth::class, $this->app->make(Firebase\Contract\Auth::class));