LinkedinProvider.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. <?php
  2. /*
  3. * This file is part of the overtrue/socialite.
  4. *
  5. * (c) overtrue <i@overtrue.me>
  6. *
  7. * This source file is subject to the MIT license that is bundled
  8. * with this source code in the file LICENSE.
  9. */
  10. namespace Overtrue\Socialite\Providers;
  11. use Overtrue\Socialite\AccessTokenInterface;
  12. use Overtrue\Socialite\ProviderInterface;
  13. use Overtrue\Socialite\User;
  14. /**
  15. * Class LinkedinProvider.
  16. *
  17. * @see https://developer.linkedin.com/docs/oauth2 [Authenticating with OAuth 2.0]
  18. */
  19. class LinkedinProvider extends AbstractProvider implements ProviderInterface
  20. {
  21. /**
  22. * The scopes being requested.
  23. *
  24. * @var array
  25. */
  26. protected $scopes = ['r_liteprofile', 'r_emailaddress'];
  27. /**
  28. * {@inheritdoc}
  29. */
  30. protected function getAuthUrl($state)
  31. {
  32. return $this->buildAuthUrlFromBase('https://www.linkedin.com/oauth/v2/authorization', $state);
  33. }
  34. /**
  35. * Get the access token for the given code.
  36. *
  37. * @param string $code
  38. *
  39. * @return \Overtrue\Socialite\AccessToken
  40. */
  41. public function getAccessToken($code)
  42. {
  43. $response = $this->getHttpClient()
  44. ->post($this->getTokenUrl(), ['form_params' => $this->getTokenFields($code)]);
  45. return $this->parseAccessToken($response->getBody());
  46. }
  47. /**
  48. * {@inheritdoc}
  49. */
  50. protected function getTokenUrl()
  51. {
  52. return 'https://www.linkedin.com/oauth/v2/accessToken';
  53. }
  54. /**
  55. * Get the POST fields for the token request.
  56. *
  57. * @param string $code
  58. *
  59. * @return array
  60. */
  61. protected function getTokenFields($code)
  62. {
  63. return parent::getTokenFields($code) + ['grant_type' => 'authorization_code'];
  64. }
  65. /**
  66. * {@inheritdoc}
  67. */
  68. protected function getUserByToken(AccessTokenInterface $token)
  69. {
  70. $basicProfile = $this->getBasicProfile($token);
  71. $emailAddress = $this->getEmailAddress($token);
  72. return array_merge($basicProfile, $emailAddress);
  73. }
  74. /**
  75. * Get the basic profile fields for the user.
  76. *
  77. * @param string $token
  78. *
  79. * @return array
  80. */
  81. protected function getBasicProfile($token)
  82. {
  83. $url = 'https://api.linkedin.com/v2/me?projection=(id,firstName,lastName,profilePicture(displayImage~:playableStreams))';
  84. $response = $this->getHttpClient()->get($url, [
  85. 'headers' => [
  86. 'Authorization' => 'Bearer '.$token,
  87. 'X-RestLi-Protocol-Version' => '2.0.0',
  88. ],
  89. ]);
  90. return (array) json_decode($response->getBody(), true);
  91. }
  92. /**
  93. * Get the email address for the user.
  94. *
  95. * @param string $token
  96. *
  97. * @return array
  98. */
  99. protected function getEmailAddress($token)
  100. {
  101. $url = 'https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))';
  102. $response = $this->getHttpClient()->get($url, [
  103. 'headers' => [
  104. 'Authorization' => 'Bearer '.$token,
  105. 'X-RestLi-Protocol-Version' => '2.0.0',
  106. ],
  107. ]);
  108. return (array) $this->arrayItem(json_decode($response->getBody(), true), 'elements.0.handle~');
  109. }
  110. /**
  111. * {@inheritdoc}
  112. */
  113. protected function mapUserToObject(array $user)
  114. {
  115. $preferredLocale = $this->arrayItem($user, 'firstName.preferredLocale.language').'_'.$this->arrayItem($user, 'firstName.preferredLocale.country');
  116. $firstName = $this->arrayItem($user, 'firstName.localized.'.$preferredLocale);
  117. $lastName = $this->arrayItem($user, 'lastName.localized.'.$preferredLocale);
  118. $name = $firstName.' '.$lastName;
  119. $images = (array) $this->arrayItem($user, 'profilePicture.displayImage~.elements', []);
  120. $avatars = array_filter($images, function ($image) {
  121. return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 100;
  122. });
  123. $avatar = array_shift($avatars);
  124. $originalAvatars = array_filter($images, function ($image) {
  125. return $image['data']['com.linkedin.digitalmedia.mediaartifact.StillImage']['storageSize']['width'] === 800;
  126. });
  127. $originalAvatar = array_shift($originalAvatars);
  128. return new User([
  129. 'id' => $this->arrayItem($user, 'id'),
  130. 'nickname' => $name,
  131. 'name' => $name,
  132. 'email' => $this->arrayItem($user, 'emailAddress'),
  133. 'avatar' => $avatar ? $this->arrayItem($avatar, 'identifiers.0.identifier') : null,
  134. 'avatar_original' => $originalAvatar ? $this->arrayItem($originalAvatar, 'identifiers.0.identifier') : null,
  135. ]);
  136. }
  137. /**
  138. * Set the user fields to request from LinkedIn.
  139. *
  140. * @param array $fields
  141. *
  142. * @return $this
  143. */
  144. public function fields(array $fields)
  145. {
  146. $this->fields = $fields;
  147. return $this;
  148. }
  149. /**
  150. * Determine if the provider is operating as stateless.
  151. *
  152. * @return bool
  153. */
  154. protected function isStateless()
  155. {
  156. return true;
  157. }
  158. }