Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support configuration of credentials with a config array #202

Merged
merged 11 commits into from
Oct 5, 2023
41 changes: 34 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.

Expand All @@ -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' => '[email protected]',
'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)
Expand Down Expand Up @@ -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 |
Expand Down
8 changes: 2 additions & 6 deletions config/firebase.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')),

/*
* ------------------------------------------------------------------------
Expand Down Expand Up @@ -180,9 +178,7 @@
*/
'timeout' => env('FIREBASE_HTTP_CLIENT_TIMEOUT'),

'guzzle_middlewares' => [

]
'guzzle_middlewares' => [],
],
],
],
Expand Down
13 changes: 7 additions & 6 deletions src/FirebaseProjectManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -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, '/');
Expand All @@ -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) {
Expand All @@ -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) {
Expand Down
57 changes: 53 additions & 4 deletions tests/FirebaseProjectManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

/**
Expand Down Expand Up @@ -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');
Expand All @@ -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' => '[email protected]',
'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
*/
Expand All @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion tests/ServiceProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down