Completed
Push — master ( 9d06c6...846137 )
by Steven
03:12
created

LinkedIn::createAccessToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 2
crap 1
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\Token\AccessToken;
10
use League\OAuth2\Client\Token\LinkedInAccessToken;
11
use League\OAuth2\Client\Tool\BearerAuthorizationTrait;
12
use Psr\Http\Message\ResponseInterface;
13
14
class LinkedIn extends AbstractProvider
15
{
16
    use BearerAuthorizationTrait;
17
18
    /**
19
     * Default scopes
20
     *
21
     * @var array
22
     */
23
    public $defaultScopes = ['r_liteprofile', 'r_emailaddress'];
24
25
    /**
26
     * Requested fields in scope, seeded with default values
27
     *
28
     * @var array
29
     * @see https://developer.linkedin.com/docs/fields/basic-profile
30
     */
31
    protected $fields = [
32
        'id', 'firstName', 'lastName', 'localizedFirstName', 'localizedLastName',
33
        'profilePicture(displayImage~:playableStreams)',
34
    ];
35
36
    /**
37
     * Constructs an OAuth 2.0 service provider.
38
     *
39
     * @param array $options An array of options to set on this provider.
40
     *     Options include `clientId`, `clientSecret`, `redirectUri`, and `state`.
41
     *     Individual providers may introduce more options, as needed.
42
     * @param array $collaborators An array of collaborators that may be used to
43
     *     override this provider's default behavior. Collaborators include
44
     *     `grantFactory`, `requestFactory`, and `httpClient`.
45
     *     Individual providers may introduce more collaborators, as needed.
46
     */
47 14
    public function __construct(array $options = [], array $collaborators = [])
48
    {
49 14
        if (isset($options['fields']) && !is_array($options['fields'])) {
50 1
            throw new InvalidArgumentException('The fields option must be an array');
51
        }
52
53 14
        parent::__construct($options, $collaborators);
54 14
    }
55
56
57
    /**
58
     * Creates an access token from a response.
59
     *
60
     * The grant that was used to fetch the response can be used to provide
61
     * additional context.
62
     *
63
     * @param  array $response
64
     * @param  AbstractGrant $grant
65
     * @return AccessTokenInterface
66
     */
67 5
    protected function createAccessToken(array $response, AbstractGrant $grant)
68
    {
69 5
        return new LinkedInAccessToken($response);
70
    }
71
72
    /**
73
     * Get the string used to separate scopes.
74
     *
75
     * @return string
76
     */
77 3
    protected function getScopeSeparator()
78
    {
79 3
        return ' ';
80
    }
81
82
    /**
83
     * Get authorization url to begin OAuth flow
84
     *
85
     * @return string
86
     */
87 3
    public function getBaseAuthorizationUrl()
88
    {
89 3
        return 'https://www.linkedin.com/oauth/v2/authorization';
90
    }
91
92
    /**
93
     * Get access token url to retrieve token
94
     *
95
     * @return string
96
     */
97 7
    public function getBaseAccessTokenUrl(array $params)
98
    {
99 7
        return 'https://www.linkedin.com/oauth/v2/accessToken';
100
    }
101
102
    /**
103
     * Get provider url to fetch user details
104
     *
105
     * @param  AccessToken $token
106
     *
107
     * @return string
108
     */
109 3
    public function getResourceOwnerDetailsUrl(AccessToken $token)
110
    {
111 3
        $query = http_build_query([
112 3
            'projection' => '(' . implode(',', $this->fields) . ')'
113
        ]);
114
115 3
        return 'https://api.linkedin.com/v2/me?' . urldecode($query);
116
    }
117
118
    /**
119
     * Get provider url to fetch user details
120
     *
121
     * @param  AccessToken $token
122
     *
123
     * @return string
124
     */
125 3
    public function getResourceOwnerEmailUrl(AccessToken $token)
0 ignored issues
show
Unused Code introduced by
The parameter $token is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
126
    {
127 3
        $query = http_build_query([
128 3
            'q' => 'members',
129
            'projection' => '(elements*(state,primary,type,handle~))'
130
        ]);
131
132 3
        return 'https://api.linkedin.com/v2/clientAwareMemberHandles?' . urldecode($query);
133
    }
134
135
    /**
136
     * Get the default scopes used by this provider.
137
     *
138
     * This should not be a complete list of all scopes, but the minimum
139
     * required for the provider user interface!
140
     *
141
     * @return array
142
     */
143 2
    protected function getDefaultScopes()
144
    {
145 2
        return $this->defaultScopes;
146
    }
147
148
    /**
149
     * Check a provider response for errors.
150
     *
151
     * @throws IdentityProviderException
152
     * @param  ResponseInterface $response
153
     * @param  string $data Parsed response data
154
     * @return void
155
     */
156 6
    protected function checkResponse(ResponseInterface $response, $data)
157
    {
158 6
        if (isset($data['error'])) {
159 1
            throw new IdentityProviderException(
160 1
                $data['error_description'] ?: $response->getReasonPhrase(),
161 1
                $response->getStatusCode(),
162 1
                $response
0 ignored issues
show
Documentation introduced by
$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...
163
            );
164
        }
165 5
    }
166
167
    /**
168
     * Generate a user object from a successful user details request.
169
     *
170
     * @param array $response
171
     * @param AccessToken $token
172
     * @return LinkedInResourceOwner
173
     */
174 2
    protected function createResourceOwner(array $response, AccessToken $token)
175
    {
176 2
        return new LinkedInResourceOwner($response);
177
    }
178
179
    /**
180
     * Returns the requested fields in scope.
181
     *
182
     * @return array
183
     */
184 3
    public function getFields()
185
    {
186 3
        return $this->fields;
187
    }
188
189
    /**
190
     * Attempts to fetch resource owner's email address via separate API request.
191
     *
192
     * @param  AccessToken $token [description]
193
     * @return string|null
194
     * @throws IdentityProviderException
195
     */
196 2
    public function getResourceOwnerEmail(AccessToken $token)
197
    {
198 2
        $emailUrl = $this->getResourceOwnerEmailUrl($token);
199 2
        $emailRequest = $this->getAuthenticatedRequest(self::METHOD_GET, $emailUrl, $token);
200 2
        $emailResponse = $this->getParsedResponse($emailRequest);
201
202 2
        return $this->extractEmailFromResponse($emailResponse);
0 ignored issues
show
Bug introduced by
It seems like $emailResponse defined by $this->getParsedResponse($emailRequest) on line 200 can also be of type null or string; however, League\OAuth2\Client\Pro...ractEmailFromResponse() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
203
    }
204
205
    /**
206
     * Updates the requested fields in scope.
207
     *
208
     * @param  array   $fields
209
     *
210
     * @return LinkedIn
211
     */
212 1
    public function withFields(array $fields)
213
    {
214 1
        $this->fields = $fields;
215
216 1
        return $this;
217
    }
218
219
    /**
220
     * Attempts to extract the email address from a valid email api response.
221
     *
222
     * @param  array  $response
223
     * @return string|null
224
     */
225
    protected function extractEmailFromResponse($response = [])
226
    {
227
        try {
228 2
            $confirmedEmails = array_filter($response['elements'], function ($element) {
229
                return
230 1
                    strtoupper($element['type']) === 'EMAIL'
231 1
                    && strtoupper($element['state']) === 'CONFIRMED'
232 1
                    && $element['primary'] === true
233 1
                    && isset($element['handle~']['emailAddress'])
234
                ;
235 2
            });
236
237 1
            return $confirmedEmails[0]['handle~']['emailAddress'];
238 1
        } catch (Exception $e) {
239 1
            return null;
240
        }
241
    }
242
}
243