From 26ab0619f5a7778ea024325199c1a91e580f8cad Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sat, 11 Dec 2021 23:28:03 +0100 Subject: [PATCH] OAuth: Allow nested info attributes --- config/config.default.php | 3 + src/Controllers/OAuthController.php | 34 ++++++-- .../Unit/Controllers/OAuthControllerTest.php | 83 ++++++++++++++++++- 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/config/config.default.php b/config/config.default.php index c3cc956c..6314d7cd 100644 --- a/config/config.default.php +++ b/config/config.default.php @@ -101,6 +101,9 @@ return [ 'last_name' => 'last-name', // User URL to provider, linked on provider settings page (optional) 'url' => '[provider page]', + // Whether info attributes are nested arrays (optional) + // For example {"user":{"name":"foo"}} can be accessed using user.name + 'nested_info' => false, // Only show after clicking the page title (optional) 'hidden' => false, // Mark user as arrived when using this provider (optional) diff --git a/src/Controllers/OAuthController.php b/src/Controllers/OAuthController.php index 4b18b360..0dd5aa9e 100644 --- a/src/Controllers/OAuthController.php +++ b/src/Controllers/OAuthController.php @@ -11,6 +11,7 @@ use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\UrlGenerator; use Engelsystem\Models\OAuth; +use Illuminate\Support\Arr; use Illuminate\Support\Collection; use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; @@ -124,7 +125,7 @@ class OAuthController extends BaseController } catch (IdentityProviderException $e) { $this->handleOAuthError($e, $providerName); } - $resourceId = $resourceOwner->getId(); + $resourceId = $this->getId($providerName, $resourceOwner); /** @var OAuth|null $oauth */ $oauth = $this->oauth @@ -156,7 +157,7 @@ class OAuthController extends BaseController if (!$oauth && $user && $connectProvider && $connectProvider == $providerName) { $oauth = new OAuth([ 'provider' => $providerName, - 'identifier' => $resourceOwner->getId(), + 'identifier' => $resourceId, 'access_token' => $accessToken->getToken(), 'refresh_token' => $accessToken->getRefreshToken(), 'expires_at' => $expirationTime, @@ -167,17 +168,22 @@ class OAuthController extends BaseController $this->log->info( 'Connected OAuth user {user} using {provider}', - ['provider' => $providerName, 'user' => $resourceOwner->getId()] + ['provider' => $providerName, 'user' => $resourceId] ); $this->addNotification('oauth.connected'); } $config = $this->config->get('oauth')[$providerName]; - $userdata = new Collection($resourceOwner->toArray()); + $resourceData = $resourceOwner->toArray(); + if (!empty($config['nested_info'])) { + $resourceData = Arr::dot($resourceData); + } + + $userdata = new Collection($resourceData); if (!$oauth) { return $this->redirectRegister( $providerName, - $resourceOwner->getId(), + $resourceId, $accessToken, $config, $userdata @@ -252,6 +258,22 @@ class OAuthController extends BaseController ); } + /** + * @param string $providerName + * @param ResourceOwner $resourceOwner + * @return mixed + */ + protected function getId(string $providerName, ResourceOwner $resourceOwner) + { + $config = $this->config->get('oauth')[$providerName]; + if (empty($config['nested_info'])) { + return $resourceOwner->getId(); + } + + $data = Arr::dot($resourceOwner->toArray()); + return $data[$config['id']]; + } + /** * @param string $provider */ @@ -299,7 +321,7 @@ class OAuthController extends BaseController 'Set user {name} ({id}) as arrived via {provider} user {user}', [ 'provider' => $providerName, - 'user' => $resourceOwner->getId(), + 'user' => $this->getId($providerName, $resourceOwner), 'name' => $user->name, 'id' => $user->id ] diff --git a/tests/Unit/Controllers/OAuthControllerTest.php b/tests/Unit/Controllers/OAuthControllerTest.php index 2395a28b..20a05625 100644 --- a/tests/Unit/Controllers/OAuthControllerTest.php +++ b/tests/Unit/Controllers/OAuthControllerTest.php @@ -83,6 +83,7 @@ class OAuthControllerTest extends TestCase * @covers \Engelsystem\Controllers\OAuthController::__construct * @covers \Engelsystem\Controllers\OAuthController::index * @covers \Engelsystem\Controllers\OAuthController::handleArrive + * @covers \Engelsystem\Controllers\OAuthController::getId */ public function testIndexArrive() { @@ -102,11 +103,9 @@ class OAuthControllerTest extends TestCase /** @var ResourceOwnerInterface|MockObject $resourceOwner */ $resourceOwner = $this->createMock(ResourceOwnerInterface::class); $this->setExpects($resourceOwner, 'toArray', null, [], $this->atLeastOnce()); - $resourceOwner->expects($this->exactly(7)) + $resourceOwner->expects($this->exactly(5)) ->method('getId') ->willReturnOnConsecutiveCalls( - 'other-provider-user-identifier', - 'other-provider-user-identifier', 'other-provider-user-identifier', 'other-provider-user-identifier', 'provider-user-identifier', @@ -444,6 +443,84 @@ class OAuthControllerTest extends TestCase $controller->index($request); } + /** + * @covers \Engelsystem\Controllers\OAuthController::index + * @covers \Engelsystem\Controllers\OAuthController::getId + * @covers \Engelsystem\Controllers\OAuthController::redirectRegister + */ + public function testIndexRedirectRegisterNestedInfo() + { + $accessToken = $this->createMock(AccessToken::class); + $this->setExpects($accessToken, 'getToken', null, 'test-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getRefreshToken', null, 'test-refresh-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getExpires', null, 4242424242, $this->atLeastOnce()); + + $config = $this->config->get('oauth'); + $config['testprovider'] = array_merge($config['testprovider'], [ + 'nested_info' => true, + 'id' => 'nested.id', + 'email' => 'nested.email', + 'username' => 'nested.name', + 'first_name' => 'nested.first', + 'last_name' => 'nested.last', + ]); + $this->config->set('oauth', $config); + + $this->config->set('registration_enabled', true); + + /** @var ResourceOwnerInterface|MockObject $resourceOwner */ + $resourceOwner = $this->createMock(ResourceOwnerInterface::class); + $this->setExpects($resourceOwner, 'getId', null, null, $this->never()); + $this->setExpects( + $resourceOwner, + 'toArray', + null, + [ + 'nested' => [ + 'id' => 'new-provider-user-identifier', + 'name' => 'testuser', + 'email' => 'foo.bar@localhost', + 'first' => 'Test', + 'last' => 'Tester', + ], + ], + $this->atLeastOnce() + ); + + /** @var GenericProvider|MockObject $provider */ + $provider = $this->createMock(GenericProvider::class); + $this->setExpects( + $provider, + 'getAccessToken', + ['authorization_code', ['code' => 'lorem-ipsum-code']], + $accessToken, + $this->atLeastOnce() + ); + $this->setExpects($provider, 'getResourceOwner', [$accessToken], $resourceOwner, $this->atLeastOnce()); + + $this->session->set('oauth2_state', 'some-internal-state'); + + $this->setExpects($this->auth, 'user', null, null, $this->atLeastOnce()); + + $this->setExpects($this->redirect, 'to', ['/register']); + + $request = new Request(); + $request = $request + ->withAttribute('provider', 'testprovider') + ->withQueryParams(['code' => 'lorem-ipsum-code', 'state' => 'some-internal-state']); + + $controller = $this->getMock(['getProvider']); + $this->setExpects($controller, 'getProvider', ['testprovider'], $provider, $this->atLeastOnce()); + + $controller->index($request); + $this->assertEquals([ + 'email' => 'foo.bar@localhost', + 'name' => 'testuser', + 'first_name' => 'Test', + 'last_name' => 'Tester', + ], $this->session->get('form_data')); + } + /** * @covers \Engelsystem\Controllers\OAuthController::connect