From 9c7f5c61e4fca7661e21098ad65c8c5179b93a87 Mon Sep 17 00:00:00 2001 From: Marijus Kilmanas Date: Thu, 15 May 2025 14:23:33 +0300 Subject: [PATCH 1/4] Add wrapper methods to find user(s) by federated user id --- src/Firebase/Auth.php | 16 ++++++++++++++++ src/Firebase/Auth/ApiClient.php | 10 ++++++++++ src/Firebase/Contract/Auth.php | 9 +++++++++ 3 files changed, 35 insertions(+) diff --git a/src/Firebase/Auth.php b/src/Firebase/Auth.php index 65a1e68b5..6820a3465 100644 --- a/src/Firebase/Auth.php +++ b/src/Firebase/Auth.php @@ -210,6 +210,22 @@ public function getUserByPhoneNumber(Stringable|string $phoneNumber): UserRecord return UserRecord::fromResponseData($data['users'][0]); } + public function getUserByProviderUid(Stringable|string $providerId, Stringable|string $providerUid): UserRecord + { + $providerId = (string) $providerId; + $providerUid = (string) $providerUid; + + $response = $this->client->getUserByProviderUid($providerId, $providerUid); + + $data = Json::decode((string) $response->getBody(), true); + + if (empty($data['users'][0])) { + throw new UserNotFound("No user with federated account ID '{$providerId}:{$providerUid}' found."); + } + + return UserRecord::fromResponseData($data['users'][0]); + } + public function createAnonymousUser(): UserRecord { return $this->createUser(CreateUser::new()); diff --git a/src/Firebase/Auth/ApiClient.php b/src/Firebase/Auth/ApiClient.php index 7775c6153..749a53bd9 100644 --- a/src/Firebase/Auth/ApiClient.php +++ b/src/Firebase/Auth/ApiClient.php @@ -118,6 +118,16 @@ public function getUserByPhoneNumber(string $phoneNumber): ResponseInterface return $this->requestApi($url, ['phoneNumber' => [$phoneNumber]]); } + /** + * @throws AuthException + */ + public function getUserByProviderUid(string $providerId, string $uid): ResponseInterface + { + $url = $this->awareAuthResourceUrlBuilder->getUrl('/accounts:lookup'); + + return $this->requestApi($url, ['federatedUserId' => [['providerId' => $providerId, 'rawId' => $uid]]]); + } + /** * @throws AuthException */ diff --git a/src/Firebase/Contract/Auth.php b/src/Firebase/Contract/Auth.php index dac2a3e56..658a417a3 100644 --- a/src/Firebase/Contract/Auth.php +++ b/src/Firebase/Contract/Auth.php @@ -125,6 +125,15 @@ public function getUserByEmail(Stringable|string $email): UserRecord; */ public function getUserByPhoneNumber(Stringable|string $phoneNumber): UserRecord; + /** + * @param Stringable|non-empty-string $providerId + * @param Stringable|non-empty-string $providerUid + * + * @throws Exception\AuthException + * @throws Exception\FirebaseException + */ + public function getUserByProviderUid(Stringable|string $providerId, Stringable|string $providerUid): UserRecord; + /** * @throws Exception\AuthException * @throws Exception\FirebaseException From b5c400225e67edbcc029b56c3c4feb36627c55d0 Mon Sep 17 00:00:00 2001 From: Marijus Kilmanas Date: Wed, 21 May 2025 12:11:02 +0300 Subject: [PATCH 2/4] Extract new function into a separate interface for BC --- src/Firebase/Auth.php | 2 +- src/Firebase/Contract/Auth.php | 9 ------- .../Transitional/FederatedUserFetcher.php | 24 +++++++++++++++++++ 3 files changed, 25 insertions(+), 10 deletions(-) create mode 100644 src/Firebase/Contract/Transitional/FederatedUserFetcher.php diff --git a/src/Firebase/Auth.php b/src/Firebase/Auth.php index 6820a3465..d079c22f3 100644 --- a/src/Firebase/Auth.php +++ b/src/Firebase/Auth.php @@ -59,7 +59,7 @@ /** * @internal */ -final class Auth implements Contract\Auth +final class Auth implements Contract\Auth, Contract\Transitional\FederatedUserFetcher { private readonly Parser $jwtParser; diff --git a/src/Firebase/Contract/Auth.php b/src/Firebase/Contract/Auth.php index 658a417a3..dac2a3e56 100644 --- a/src/Firebase/Contract/Auth.php +++ b/src/Firebase/Contract/Auth.php @@ -125,15 +125,6 @@ public function getUserByEmail(Stringable|string $email): UserRecord; */ public function getUserByPhoneNumber(Stringable|string $phoneNumber): UserRecord; - /** - * @param Stringable|non-empty-string $providerId - * @param Stringable|non-empty-string $providerUid - * - * @throws Exception\AuthException - * @throws Exception\FirebaseException - */ - public function getUserByProviderUid(Stringable|string $providerId, Stringable|string $providerUid): UserRecord; - /** * @throws Exception\AuthException * @throws Exception\FirebaseException diff --git a/src/Firebase/Contract/Transitional/FederatedUserFetcher.php b/src/Firebase/Contract/Transitional/FederatedUserFetcher.php new file mode 100644 index 000000000..c894ddb01 --- /dev/null +++ b/src/Firebase/Contract/Transitional/FederatedUserFetcher.php @@ -0,0 +1,24 @@ + Date: Thu, 5 Jun 2025 15:14:58 +0300 Subject: [PATCH 3/4] Add negative test case for Auth::getUserByProviderUid(); Stabilize unit test timezone --- composer.json | 8 ++++---- tests/Integration/AuthTestCase.php | 10 ++++++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index d2749da06..7f4a1cbf6 100644 --- a/composer.json +++ b/composer.json @@ -110,7 +110,7 @@ "Composer\\Config::disableProcessTimeout", "@analyze", "@test-dependencies", - "vendor/bin/phpunit --stop-on-error --stop-on-failure" + "php -d date.timezone=UTC vendor/bin/phpunit --stop-on-error --stop-on-failure" ], "test-bc": [ "docker run -it --rm -v `pwd`:/app nyholm/roave-bc-check" @@ -120,16 +120,16 @@ ], "test-coverage": [ "Composer\\Config::disableProcessTimeout", - "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=.build/coverage" + "XDEBUG_MODE=coverage php -d date.timezone=UTC vendor/bin/phpunit --coverage-html=.build/coverage" ], "test-emulator": [ "FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 FIREBASE_DATABASE_EMULATOR_HOST=localhost:9100 firebase emulators:exec --only auth,database --project beste-firebase 'XDEBUG_MODE=coverage vendor/bin/phpunit --group=emulator'" ], "test-units": [ - "vendor/bin/phpunit --testsuite=unit" + "php -d date.timezone=UTC vendor/bin/phpunit --testsuite=unit" ], "test-integration": [ - "vendor/bin/phpunit --testsuite=integration" + "php -d date.timezone=UTC vendor/bin/phpunit --testsuite=integration" ] }, "extra": { diff --git a/tests/Integration/AuthTestCase.php b/tests/Integration/AuthTestCase.php index 564c5f29f..303354b49 100644 --- a/tests/Integration/AuthTestCase.php +++ b/tests/Integration/AuthTestCase.php @@ -520,6 +520,16 @@ public function getUserByNonExistingPhoneNumber(): void $this->auth->getUserByPhoneNumber($phoneNumber); } + #[Test] + public function getUserByNonExistingProviderUid(): void + { + $provider = 'test.com'; + $uid = 'u'.random_int(1000000, 9999999); + + $this->expectException(UserNotFound::class); + $this->auth->getUserByProviderUid($provider, $uid); + } + #[Test] public function createUser(): void { From 11f6c62567c84a21566ce9fc032b27732383e5e5 Mon Sep 17 00:00:00 2001 From: Marijus Kilmanas Date: Thu, 5 Jun 2025 19:13:37 +0300 Subject: [PATCH 4/4] Fix phpstan reported type mismatch; change phpunit timezone solution --- composer.json | 8 ++++---- phpunit.xml.dist | 4 ++++ tests/Integration/AuthTestCase.php | 14 ++++++++++---- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 7f4a1cbf6..d2749da06 100644 --- a/composer.json +++ b/composer.json @@ -110,7 +110,7 @@ "Composer\\Config::disableProcessTimeout", "@analyze", "@test-dependencies", - "php -d date.timezone=UTC vendor/bin/phpunit --stop-on-error --stop-on-failure" + "vendor/bin/phpunit --stop-on-error --stop-on-failure" ], "test-bc": [ "docker run -it --rm -v `pwd`:/app nyholm/roave-bc-check" @@ -120,16 +120,16 @@ ], "test-coverage": [ "Composer\\Config::disableProcessTimeout", - "XDEBUG_MODE=coverage php -d date.timezone=UTC vendor/bin/phpunit --coverage-html=.build/coverage" + "XDEBUG_MODE=coverage vendor/bin/phpunit --coverage-html=.build/coverage" ], "test-emulator": [ "FIREBASE_AUTH_EMULATOR_HOST=localhost:9099 FIREBASE_DATABASE_EMULATOR_HOST=localhost:9100 firebase emulators:exec --only auth,database --project beste-firebase 'XDEBUG_MODE=coverage vendor/bin/phpunit --group=emulator'" ], "test-units": [ - "php -d date.timezone=UTC vendor/bin/phpunit --testsuite=unit" + "vendor/bin/phpunit --testsuite=unit" ], "test-integration": [ - "php -d date.timezone=UTC vendor/bin/phpunit --testsuite=integration" + "vendor/bin/phpunit --testsuite=integration" ] }, "extra": { diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 415a835b4..60553299c 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,6 +6,10 @@ bootstrap="tests/bootstrap.php" colors="true" > + + + + tests/Unit diff --git a/tests/Integration/AuthTestCase.php b/tests/Integration/AuthTestCase.php index 303354b49..1774afb42 100644 --- a/tests/Integration/AuthTestCase.php +++ b/tests/Integration/AuthTestCase.php @@ -14,6 +14,7 @@ use Kreait\Firebase\Auth\SignIn\FailedToSignIn; use Kreait\Firebase\Auth\UserRecord; use Kreait\Firebase\Contract\Auth; +use Kreait\Firebase\Contract\Transitional\FederatedUserFetcher; use Kreait\Firebase\Exception\Auth\InvalidOobCode; use Kreait\Firebase\Exception\Auth\RevokedIdToken; use Kreait\Firebase\Exception\Auth\RevokedSessionCookie; @@ -523,11 +524,16 @@ public function getUserByNonExistingPhoneNumber(): void #[Test] public function getUserByNonExistingProviderUid(): void { - $provider = 'test.com'; - $uid = 'u'.random_int(1000000, 9999999); + if ($this->auth instanceof FederatedUserFetcher) { + $provider = 'test.com'; + $uid = 'u' . random_int(1000000, 9999999); - $this->expectException(UserNotFound::class); - $this->auth->getUserByProviderUid($provider, $uid); + $this->expectException(UserNotFound::class); + /** @phpstan-ignore method.notFound */ + $this->auth->getUserByProviderUid($provider, $uid); + } else { + $this->fail("{\$this->auth} does not implement FederatedUserFetcher as expected - cannot test"); + } } #[Test]