OAuth: Allow nested info attributes

This commit is contained in:
Igor Scheller 2021-12-11 23:28:03 +01:00 committed by msquare
parent 8256b9d6bd
commit 26ab0619f5
3 changed files with 111 additions and 9 deletions

View File

@ -101,6 +101,9 @@ return [
'last_name' => 'last-name', 'last_name' => 'last-name',
// User URL to provider, linked on provider settings page (optional) // User URL to provider, linked on provider settings page (optional)
'url' => '[provider page]', '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) // Only show after clicking the page title (optional)
'hidden' => false, 'hidden' => false,
// Mark user as arrived when using this provider (optional) // Mark user as arrived when using this provider (optional)

View File

@ -11,6 +11,7 @@ use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\UrlGenerator; use Engelsystem\Http\UrlGenerator;
use Engelsystem\Models\OAuth; use Engelsystem\Models\OAuth;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\AbstractProvider;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
@ -124,7 +125,7 @@ class OAuthController extends BaseController
} catch (IdentityProviderException $e) { } catch (IdentityProviderException $e) {
$this->handleOAuthError($e, $providerName); $this->handleOAuthError($e, $providerName);
} }
$resourceId = $resourceOwner->getId(); $resourceId = $this->getId($providerName, $resourceOwner);
/** @var OAuth|null $oauth */ /** @var OAuth|null $oauth */
$oauth = $this->oauth $oauth = $this->oauth
@ -156,7 +157,7 @@ class OAuthController extends BaseController
if (!$oauth && $user && $connectProvider && $connectProvider == $providerName) { if (!$oauth && $user && $connectProvider && $connectProvider == $providerName) {
$oauth = new OAuth([ $oauth = new OAuth([
'provider' => $providerName, 'provider' => $providerName,
'identifier' => $resourceOwner->getId(), 'identifier' => $resourceId,
'access_token' => $accessToken->getToken(), 'access_token' => $accessToken->getToken(),
'refresh_token' => $accessToken->getRefreshToken(), 'refresh_token' => $accessToken->getRefreshToken(),
'expires_at' => $expirationTime, 'expires_at' => $expirationTime,
@ -167,17 +168,22 @@ class OAuthController extends BaseController
$this->log->info( $this->log->info(
'Connected OAuth user {user} using {provider}', 'Connected OAuth user {user} using {provider}',
['provider' => $providerName, 'user' => $resourceOwner->getId()] ['provider' => $providerName, 'user' => $resourceId]
); );
$this->addNotification('oauth.connected'); $this->addNotification('oauth.connected');
} }
$config = $this->config->get('oauth')[$providerName]; $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) { if (!$oauth) {
return $this->redirectRegister( return $this->redirectRegister(
$providerName, $providerName,
$resourceOwner->getId(), $resourceId,
$accessToken, $accessToken,
$config, $config,
$userdata $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 * @param string $provider
*/ */
@ -299,7 +321,7 @@ class OAuthController extends BaseController
'Set user {name} ({id}) as arrived via {provider} user {user}', 'Set user {name} ({id}) as arrived via {provider} user {user}',
[ [
'provider' => $providerName, 'provider' => $providerName,
'user' => $resourceOwner->getId(), 'user' => $this->getId($providerName, $resourceOwner),
'name' => $user->name, 'name' => $user->name,
'id' => $user->id 'id' => $user->id
] ]

View File

@ -83,6 +83,7 @@ class OAuthControllerTest extends TestCase
* @covers \Engelsystem\Controllers\OAuthController::__construct * @covers \Engelsystem\Controllers\OAuthController::__construct
* @covers \Engelsystem\Controllers\OAuthController::index * @covers \Engelsystem\Controllers\OAuthController::index
* @covers \Engelsystem\Controllers\OAuthController::handleArrive * @covers \Engelsystem\Controllers\OAuthController::handleArrive
* @covers \Engelsystem\Controllers\OAuthController::getId
*/ */
public function testIndexArrive() public function testIndexArrive()
{ {
@ -102,11 +103,9 @@ class OAuthControllerTest extends TestCase
/** @var ResourceOwnerInterface|MockObject $resourceOwner */ /** @var ResourceOwnerInterface|MockObject $resourceOwner */
$resourceOwner = $this->createMock(ResourceOwnerInterface::class); $resourceOwner = $this->createMock(ResourceOwnerInterface::class);
$this->setExpects($resourceOwner, 'toArray', null, [], $this->atLeastOnce()); $this->setExpects($resourceOwner, 'toArray', null, [], $this->atLeastOnce());
$resourceOwner->expects($this->exactly(7)) $resourceOwner->expects($this->exactly(5))
->method('getId') ->method('getId')
->willReturnOnConsecutiveCalls( ->willReturnOnConsecutiveCalls(
'other-provider-user-identifier',
'other-provider-user-identifier',
'other-provider-user-identifier', 'other-provider-user-identifier',
'other-provider-user-identifier', 'other-provider-user-identifier',
'provider-user-identifier', 'provider-user-identifier',
@ -444,6 +443,84 @@ class OAuthControllerTest extends TestCase
$controller->index($request); $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 * @covers \Engelsystem\Controllers\OAuthController::connect