From bb62ec0b78d9576bebfbf74008b0c9da575e4976 Mon Sep 17 00:00:00 2001 From: Peter Dulacka Date: Tue, 29 Sep 2020 06:22:45 +0000 Subject: [PATCH] Allow protection of access tokens before device token unpaid This protection is necessary to prevent unwanted unpairing of backend-created access tokens serving for device-inapp purchase connection. Commit covers scenario: - Login with device token - Verify inapp purchase - Logout (device is still linked to the purchase thanks to backend-only created access token during logout process) - Login with other account At this point, device should still be able to access the content, but without the protection implemented here it wouldn't. remp/crm#1494 --- src/DataProviders/AccessTokenDataProvider.php | 23 ++++ src/GooglePlayBillingModule.php | 9 ++ src/Tests/AccessTokenDataProviderTest.php | 104 ++++++++++++++++++ src/api/VerifyPurchaseApiHandler.php | 6 +- src/config/config.neon | 1 + 5 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 src/DataProviders/AccessTokenDataProvider.php create mode 100644 src/Tests/AccessTokenDataProviderTest.php diff --git a/src/DataProviders/AccessTokenDataProvider.php b/src/DataProviders/AccessTokenDataProvider.php new file mode 100644 index 0000000..b748c58 --- /dev/null +++ b/src/DataProviders/AccessTokenDataProvider.php @@ -0,0 +1,23 @@ +source === GooglePlayBillingModule::USER_SOURCE_APP) { + return false; + } + return true; + } + + public function provide(array $params) + { + throw new \Exception('AccessTokenDataProvider does not provide generic method results'); + } +} diff --git a/src/GooglePlayBillingModule.php b/src/GooglePlayBillingModule.php index da258fd..df6f406 100644 --- a/src/GooglePlayBillingModule.php +++ b/src/GooglePlayBillingModule.php @@ -6,6 +6,7 @@ use Crm\ApiModule\Router\ApiIdentifier; use Crm\ApiModule\Router\ApiRoute; use Crm\ApplicationModule\CrmModule; +use Crm\ApplicationModule\DataProvider\DataProviderManager; use Crm\ApplicationModule\SeederManager; use Crm\GooglePlayBillingModule\Api\VerifyPurchaseApiHandler; use Crm\UsersModule\Auth\UserTokenAuthorization; @@ -64,4 +65,12 @@ public function registerEventHandlers(Emitter $emitter) $this->getInstance(\Crm\GooglePlayBillingModule\Events\PairDeviceAccessTokensEventHandler::class) ); } + + public function registerDataProviders(DataProviderManager $dataProviderManager) + { + $dataProviderManager->registerDataProvider( + 'users.dataprovider.access_tokens', + $this->getInstance(\Crm\GooglePlayBillingModule\DataProviders\AccessTokenDataProvider::class) + ); + } } diff --git a/src/Tests/AccessTokenDataProviderTest.php b/src/Tests/AccessTokenDataProviderTest.php new file mode 100644 index 0000000..520fdb6 --- /dev/null +++ b/src/Tests/AccessTokenDataProviderTest.php @@ -0,0 +1,104 @@ +accessTokensRepository = $this->getRepository(AccessTokensRepository::class); + $this->deviceTokensRepository = $this->getRepository(DeviceTokensRepository::class); + $this->usersRepository = $this->getRepository(UsersRepository::class); + + $dataProviderManager = $this->inject(DataProviderManager::class); + $dataProviderManager->registerDataProvider( + 'users.dataprovider.access_tokens', + $this->inject(\Crm\GooglePlayBillingModule\DataProviders\AccessTokenDataProvider::class) + ); + } + + public function testUnprotectedUnpairing() + { + $deviceToken = $this->deviceTokensRepository->generate('foo'); + + // pair users + $user1 = $this->getUser(); + $accessToken1 = $this->accessTokensRepository->add($user1); + $this->accessTokensRepository->pairWithDeviceToken($accessToken1, $deviceToken); + $user2 = $this->getUser(); + $accessToken2 = $this->accessTokensRepository->add($user2); + $this->accessTokensRepository->pairWithDeviceToken($accessToken2, $deviceToken); + + // unpair token + $this->accessTokensRepository->unpairDeviceToken($deviceToken); + + // regular unpairing should get rid of all access tokens linked to the device + $this->assertCount( + 0, + $this->accessTokensRepository->findAllByDeviceToken($deviceToken) + ); + } + + public function testProtectedUnpairing() + { + $deviceToken = $this->deviceTokensRepository->generate('foo'); + + // pair users, protected second + $user1 = $this->getUser(); + $accessToken1 = $this->accessTokensRepository->add($user1); + $this->accessTokensRepository->pairWithDeviceToken($accessToken1, $deviceToken); + $user2 = $this->getUser(); + $accessToken2 = $this->accessTokensRepository->add($user2, 3, GooglePlayBillingModule::USER_SOURCE_APP); + $this->accessTokensRepository->pairWithDeviceToken($accessToken2, $deviceToken); + + // unpair token + $this->accessTokensRepository->unpairDeviceToken($deviceToken); + + // unpairing should get rid of the one access token without Google source + $this->assertCount( + 1, + $this->accessTokensRepository->findAllByDeviceToken($deviceToken) + ); + } + + private function getUser() + { + return $this->usersRepository->add('user_' . Random::generate() . '@example.com', 'secret', '', ''); + } +} diff --git a/src/api/VerifyPurchaseApiHandler.php b/src/api/VerifyPurchaseApiHandler.php index e6ccbff..6bfe044 100644 --- a/src/api/VerifyPurchaseApiHandler.php +++ b/src/api/VerifyPurchaseApiHandler.php @@ -424,7 +424,11 @@ public function getUserFromSubscriptionResponse(SubscriptionResponse $subscripti if (!$userId) { return null; } - return $this->usersRepository->find($userId); + $user = $this->usersRepository->find($userId); + if (!$user) { + return null; + } + return $user; } private function getUserIdFromSubscriptionResponse(SubscriptionResponse $subscriptionResponse): ?string diff --git a/src/config/config.neon b/src/config/config.neon index ff2ba70..95cf758 100644 --- a/src/config/config.neon +++ b/src/config/config.neon @@ -11,6 +11,7 @@ services: - Crm\GooglePlayBillingModule\Api\DeveloperNotificationPushWebhookApiHandler - Crm\GooglePlayBillingModule\Api\VerifyPurchaseApiHandler + - Crm\GooglePlayBillingModule\DataProviders\AccessTokenDataProvider - Crm\GooglePlayBillingModule\Events\PairDeviceAccessTokensEventHandler - Crm\GooglePlayBillingModule\Events\RemovedAccessTokenEventHandler - Crm\GooglePlayBillingModule\Gateways\GooglePlayBilling