Completed
Push — master ( 3d68f3...9e9ea2 )
by Steven
15s queued 11s
created

src/Provider/LinkedIn.php (1 issue)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace League\OAuth2\Client\Provider;
4
5
use Exception;
6
use InvalidArgumentException;
7
use League\OAuth2\Client\Grant\AbstractGrant;
8
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
9
use League\OAuth2\Client\Provider\Exception\LinkedInAccessDeniedException;
10
use League\OAuth2\Client\Token\AccessToken;
11
use League\OAuth2\Client\Token\LinkedInAccessToken;
12
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
13
use Psr\Http\Message\ResponseInterface;
14
15
class LinkedIn extends AbstractProvider
16
{
17
    use BearerAuthorizationTrait;
18
19
    /**
20
     * Default scopes
21
     *
22
     * @var array
23
     */
24
    public $defaultScopes = ['r_liteprofile', 'r_emailaddress'];
25
26
    /**
27
     * Requested fields in scope, seeded with default values
28
     *
29
     * @var array
30
     * @see https://developer.linkedin.com/docs/fields/basic-profile
31
     */
32
    protected $fields = [
33
        'id', 'firstName', 'lastName', 'localizedFirstName', 'localizedLastName',
34
        'profilePicture(displayImage~:playableStreams)',
35
    ];
36
37
    /**
38
     * Constructs an OAuth 2.0 service provider.
39
     *
40
     * @param array $options An array of options to set on this provider.
41
     *     Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
42
     *     Individual providers may introduce more options, as needed.
43
     * @param array $collaborators An array of collaborators that may be used to
44
     *     override this provider's default behavior. Collaborators include
45
     *     `grantFactory`, `requestFactory`, and `httpClient`.
46
     *     Individual providers may introduce more collaborators, as needed.
47
     */
48 16
    public function __construct(array $options = [], array $collaborators = [])
49
    {
50 16
        if (isset($options['fields']) && !is_array($options['fields'])) {
51 1
            throw new InvalidArgumentException('The fields option must be an array');
52
        }
53
54 16
        parent::__construct($options, $collaborators);
55 16
    }
56
57
58
    /**
59
     * Creates an access token from a response.
60
     *
61
     * The grant that was used to fetch the response can be used to provide
62
     * additional context.
63
     *
64
     * @param  array $response
65
     * @param  AbstractGrant $grant
66
     * @return AccessTokenInterface
67
     */
68 7
    protected function createAccessToken(array $response, AbstractGrant $grant)
69
    {
70 7
        return new LinkedInAccessToken($response);
71
    }
72
73
    /**
74
     * Get the string used to separate scopes.
75
     *
76
     * @return string
77
     */
78 3
    protected function getScopeSeparator()
79
    {
80 3
        return ' ';
81
    }
82
83
    /**
84
     * Get authorization url to begin OAuth flow
85
     *
86
     * @return string
87
     */
88 3
    public function getBaseAuthorizationUrl()
89
    {
90 3
        return 'https://www.linkedin.com/oauth/v2/authorization';
91
    }
92
93
    /**
94
     * Get access token url to retrieve token
95
     *
96
     * @return string
97
     */
98 9
    public function getBaseAccessTokenUrl(array $params)
99
    {
100 9
        return 'https://www.linkedin.com/oauth/v2/accessToken';
101
    }
102
103
    /**
104
     * Get provider url to fetch user details
105
     *
106
     * @param  AccessToken $token
107
     *
108
     * @return string
109
     */
110 4
    public function getResourceOwnerDetailsUrl(AccessToken $token)
111
    {
112 4
        $query = http_build_query([
113 4
            'projection' => '(' . implode(',', $this->fields) . ')'
114
        ]);
115
116 4
        return 'https://api.linkedin.com/v2/me?' . urldecode($query);
117
    }
118
119
    /**
120
     * Get provider url to fetch user details
121
     *
122
     * @param  AccessToken $token
123
     *
124
     * @return string
125
     */
126 7
    public function getResourceOwnerEmailUrl(AccessToken $token)
127
    {
128 7
        $query = http_build_query([
129 7
            'q' => 'members',
130
            'projection' => '(elements*(state,primary,type,handle~))'
131
        ]);
132
133 7
        return 'https://api.linkedin.com/v2/clientAwareMemberHandles?' . urldecode($query);
134
    }
135
136
    /**
137
     * Get the default scopes used by this provider.
138
     *
139
     * This should not be a complete list of all scopes, but the minimum
140
     * required for the provider user interface!
141
     *
142
     * @return array
143
     */
144 2
    protected function getDefaultScopes()
145
    {
146 2
        return $this->defaultScopes;
147
    }
148
149
    /**
150
     * Check a provider response for errors.
151
     *
152
     * @throws IdentityProviderException
153
     * @param  ResponseInterface $response
154
     * @param  array $data Parsed response data
155
     * @return void
156
     */
157 8
    protected function checkResponse(ResponseInterface $response, $data)
158
    {
159
        // https://developer.linkedin.com/docs/guide/v2/error-handling
160 8
        if ($response->getStatusCode() >= 400) {
161 3
            if (isset($data['status']) && $data['status'] === 403) {
162 2
                throw new LinkedInAccessDeniedException(
163 2
                    $data['message'] ?: $response->getReasonPhrase(),
164 2
                    $response->getStatusCode(),
165 2
                    $response
166
                );
167
            }
168
169 1
            throw new IdentityProviderException(
170 1
                $data['message'] ?: $response->getReasonPhrase(),
171 1
                $data['status'] ?: $response->getStatusCode(),
172 1
                $response
0 ignored issues
show
$response is of type object<Psr\Http\Message\ResponseInterface>, but the function expects a array|string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
173
            );
174
        }
175 7
    }
176
177
    /**
178
     * Generate a user object from a successful user details request.
179
     *
180
     * @param array $response
181
     * @param AccessToken $token
182
     * @return LinkedInResourceOwner
183
     */
184 3
    protected function createResourceOwner(array $response, AccessToken $token)
185
    {
186
        // If current accessToken is not authorized with r_emailaddress scope,
187
        // getResourceOwnerEmail will throw LinkedInAccessDeniedException, it will be caught here,
188
        // and then the email will be set to null
189
        // When email is not available due to chosen scopes, other providers simply set it to null, let's do the same.
190
        try {
191 3
            $email = $this->getResourceOwnerEmail($token);
192 1
        } catch (LinkedInAccessDeniedException $exception) {
193 1
            $email = null;
194
        }
195
196 3
        return new LinkedInResourceOwner($response, $email);
197
    }
198
199
    /**
200
     * Returns the requested fields in scope.
201
     *
202
     * @return array
203
     */
204 3
    public function getFields()
205
    {
206 3
        return $this->fields;
207
    }
208
209
    /**
210
     * Attempts to fetch resource owner's email address via separate API request.
211
     *
212
     * @param  AccessToken $token [description]
213
     * @return string|null
214
     * @throws IdentityProviderException
215
     */
216 6
    public function getResourceOwnerEmail(AccessToken $token)
217
    {
218 6
        $emailUrl = $this->getResourceOwnerEmailUrl($token);
219 6
        $emailRequest = $this->getAuthenticatedRequest(self::METHOD_GET, $emailUrl, $token);
220 6
        $emailResponse = $this->getParsedResponse($emailRequest);
221
222 4
        return $this->extractEmailFromResponse($emailResponse);
223
    }
224
225
    /**
226
     * Updates the requested fields in scope.
227
     *
228
     * @param  array   $fields
229
     *
230
     * @return LinkedIn
231
     */
232 1
    public function withFields(array $fields)
233
    {
234 1
        $this->fields = $fields;
235
236 1
        return $this;
237
    }
238
239
    /**
240
     * Attempts to extract the email address from a valid email api response.
241
     *
242
     * @param  array  $response
243
     * @return string|null
244
     */
245
    protected function extractEmailFromResponse($response = [])
246
    {
247
        try {
248 4
            $confirmedEmails = array_filter($response['elements'], function ($element) {
249
                return
250 3
                    strtoupper($element['type']) === 'EMAIL'
251 3
                    && strtoupper($element['state']) === 'CONFIRMED'
252 3
                    && $element['primary'] === true
253 3
                    && isset($element['handle~']['emailAddress'])
254
                ;
255 4
            });
256
257 3
            return $confirmedEmails[0]['handle~']['emailAddress'];
258 1
        } catch (Exception $e) {
259 1
            return null;
260
        }
261
    }
262
}
263