Skip to content

Commit

Permalink
[5.x] Adds LinkedInOpenId provider (#662)
Browse files Browse the repository at this point in the history
* Adds `LinkedInOpenId` provider

* Apply fixes from StyleCI

---------

Co-authored-by: StyleCI Bot <[email protected]>
  • Loading branch information
nunomaduro and StyleCIBot authored Sep 5, 2023
1 parent 1e81f39 commit 14acfa3
Show file tree
Hide file tree
Showing 3 changed files with 217 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/SocialiteManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Laravel\Socialite\Two\GithubProvider;
use Laravel\Socialite\Two\GitlabProvider;
use Laravel\Socialite\Two\GoogleProvider;
use Laravel\Socialite\Two\LinkedInOpenIdProvider;
use Laravel\Socialite\Two\LinkedInProvider;
use Laravel\Socialite\Two\SlackProvider;
use Laravel\Socialite\Two\TwitterProvider as TwitterOAuth2Provider;
Expand Down Expand Up @@ -95,6 +96,20 @@ protected function createLinkedinDriver()
);
}

/**
* Create an instance of the specified driver.
*
* @return \Laravel\Socialite\Two\AbstractProvider
*/
protected function createLinkedinOpenidDriver()
{
$config = $this->config->get('services.linkedin-openid');

return $this->buildProvider(
LinkedInOpenIdProvider::class, $config
);
}

/**
* Create an instance of the specified driver.
*
Expand Down
84 changes: 84 additions & 0 deletions src/Two/LinkedInOpenIdProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

namespace Laravel\Socialite\Two;

use GuzzleHttp\RequestOptions;

class LinkedInOpenIdProvider extends AbstractProvider implements ProviderInterface
{
/**
* The scopes being requested.
*
* @var array
*/
protected $scopes = ['openid', 'profile', 'email'];

/**
* The separating character for the requested scopes.
*
* @var string
*/
protected $scopeSeparator = ' ';

/**
* {@inheritdoc}
*/
protected function getAuthUrl($state)
{
return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
}

/**
* {@inheritdoc}
*/
protected function getTokenUrl()
{
return 'https://www.linkedin.com/oauth/v2/accessToken';
}

/**
* {@inheritdoc}
*/
protected function getUserByToken($token)
{
return $this->getBasicProfile($token);
}

/**
* Get the basic profile fields for the user.
*
* @param string $token
* @return array
*/
protected function getBasicProfile($token)
{
$response = $this->getHttpClient()->get('https://api.linkedin.com/v2/userinfo', [
RequestOptions::HEADERS => [
'Authorization' => 'Bearer '.$token,
'X-RestLi-Protocol-Version' => '2.0.0',
],
RequestOptions::QUERY => [
'projection' => '(sub,email,name,given_name,family_name,picture)',
],
]);

return (array) json_decode($response->getBody(), true);
}

/**
* {@inheritdoc}
*/
protected function mapUserToObject(array $user)
{
return (new User)->setRaw($user)->map([
'id' => $user['sub'],
'nickname' => null,
'name' => $user['name'],
'first_name' => $user['given_name'],
'last_name' => $user['family_name'],
'email' => $user['email'] ?? null,
'avatar' => $user['picture'] ?? null,
'avatar_original' => $user['picture'] ?? null,
]);
}
}
118 changes: 118 additions & 0 deletions tests/LinkedInOpenIdProviderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Laravel\Socialite\Tests;

use GuzzleHttp\Client;
use GuzzleHttp\RequestOptions;
use Illuminate\Http\Request;
use Laravel\Socialite\Contracts\User as UserContract;
use Laravel\Socialite\Two\LinkedInOpenIdProvider;
use Laravel\Socialite\Two\User;
use Mockery as m;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;

class LinkedInOpenIdProviderTest extends TestCase
{
protected function tearDown(): void
{
parent::tearDown();

m::close();
}

public function test_response()
{
$user = $this->fromResponse([
'sub' => 'asdfgh',
'given_name' => 'Nuno',
'picture' => 'https://media.licdn.com/dms/image/D4D03AQmZFgJNqeNNk',
'name' => 'Nuno Maduro',
'family_name' => 'Maduro',
'email' => '[email protected]',
]);

$this->assertInstanceOf(User::class, $user);
$this->assertSame('asdfgh', $user->getId());
$this->assertNull($user->getNickname());
$this->assertSame('Nuno Maduro', $user->getName());
$this->assertSame('[email protected]', $user->getEmail());
$this->assertSame('https://media.licdn.com/dms/image/D4D03AQmZFgJNqeNNk', $user->getAvatar());

$this->assertSame([
'id' => 'asdfgh',
'nickname' => null,
'name' => 'Nuno Maduro',
'first_name' => 'Nuno',
'last_name' => 'Maduro',
'email' => '[email protected]',
'avatar' => 'https://media.licdn.com/dms/image/D4D03AQmZFgJNqeNNk',
'avatar_original' => 'https://media.licdn.com/dms/image/D4D03AQmZFgJNqeNNk',
], $user->attributes);
}

public function test_missing_email_and_avatar()
{
$user = $this->fromResponse([
'sub' => 'asdfgh',
'given_name' => 'Nuno',
'name' => 'Nuno Maduro',
'family_name' => 'Maduro',
]);

$this->assertInstanceOf(User::class, $user);
$this->assertSame('asdfgh', $user->getId());
$this->assertNull($user->getNickname());
$this->assertSame('Nuno Maduro', $user->getName());
$this->assertNull($user->getEmail());
$this->assertNull($user->getAvatar());

$this->assertSame([
'id' => 'asdfgh',
'nickname' => null,
'name' => 'Nuno Maduro',
'first_name' => 'Nuno',
'last_name' => 'Maduro',
'email' => null,
'avatar' => null,
'avatar_original' => null,
], $user->attributes);
}

protected function fromResponse(array $response): UserContract
{
$request = m::mock(Request::class);
$request->allows('input')->with('code')->andReturns('fake-code');

$stream = m::mock(StreamInterface::class);
$stream->allows('__toString')->andReturns(json_encode(['access_token' => 'fake-token']));

$accessTokenResponse = m::mock(ResponseInterface::class);
$accessTokenResponse->allows('getBody')->andReturns($stream);

$basicProfileStream = m::mock(StreamInterface::class);
$basicProfileStream->allows('__toString')->andReturns(json_encode($response));

$basicProfileResponse = m::mock(ResponseInterface::class);
$basicProfileResponse->allows('getBody')->andReturns($basicProfileStream);

$guzzle = m::mock(Client::class);
$guzzle->expects('post')->andReturns($accessTokenResponse);
$guzzle->allows('get')->with('https://api.linkedin.com/v2/userinfo', [
RequestOptions::HEADERS => [
'Authorization' => 'Bearer fake-token',
'X-RestLi-Protocol-Version' => '2.0.0',
],
RequestOptions::QUERY => [
'projection' => '(sub,email,name,given_name,family_name,picture)',
],
])->andReturns($basicProfileResponse);

$provider = new LinkedInOpenIdProvider($request, 'client_id', 'client_secret', 'redirect');
$provider->stateless();
$provider->setHttpClient($guzzle);

return $provider->user();
}
}

0 comments on commit 14acfa3

Please sign in to comment.