Completed
Pull Request — master (#73)
by Tobias
02:16
created

Authenticator::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4286
cc 1
eloc 4
nc 1
nop 3
1
<?php
2
3
namespace Happyr\LinkedIn;
4
5
use Happyr\LinkedIn\Exceptions\LinkedInApiException;
6
use Happyr\LinkedIn\Http\GlobalVariableGetter;
7
use Happyr\LinkedIn\Http\RequestManager;
8
use Happyr\LinkedIn\Http\ResponseConverter;
9
use Happyr\LinkedIn\Http\UrlGeneratorInterface;
10
use Happyr\LinkedIn\Storage\DataStorageInterface;
11
use Happyr\LinkedIn\Storage\SessionStorage;
12
13
class Authenticator
14
{
15
    /**
16
     * The Application ID.
17
     *
18
     * @var string
19
     */
20
    protected $appId;
21
22
    /**
23
     * The Application App Secret.
24
     *
25
     * @var string
26
     */
27
    protected $appSecret;
28
29
    /**
30
     * A CSRF state variable to assist in the defense against CSRF attacks.
31
     */
32
    protected $state;
33
34
    /**
35
     * @var DataStorageInterface storage
36
     */
37
    private $storage;
38
39
    /**
40
     * @var RequestManager
41
     */
42
    private $requestManager;
43
44
    /**
45
     * @param RequestManager $requestManager
46
     * @param string         $appId
47
     * @param string         $appSecret
48
     */
49
    public function __construct(RequestManager $requestManager, $appId, $appSecret)
50
    {
51
        $this->appId = $appId;
52
        $this->appSecret = $appSecret;
53
        $this->requestManager = $requestManager;
54
    }
55
56
    /**
57
     * Determines and returns the user access token using the authorization code. The intent is to
58
     * return a valid access token, or null if one is determined to not be available.
59
     *
60
     * @param UrlGeneratorInterface $urlGenerator
61
     *
62
     * @return AccessToken|null A valid user access token, or null if one could not be determined.
63
     *
64
     * @throws LinkedInApiException
65
     */
66
    public function fetchNewAccessToken(UrlGeneratorInterface $urlGenerator)
67
    {
68
        $storage = $this->getStorage();
69
        $code = $this->getCode();
70
71
        if ($code !== null) {
72
            $accessToken = $this->getAccessTokenFromCode($urlGenerator, $code);
73
            if ($accessToken) {
74
                $storage->set('code', $code);
75
                $storage->set('access_token', $accessToken);
76
77
                return $accessToken;
78
            }
79
80
            // code was bogus, so everything based on it should be invalidated.
81
            $storage->clearAll();
82
            throw new LinkedInApiException('Could not get access token');
83
        }
84
85
        // as a fallback, just return whatever is in the persistent
86
        // store, knowing nothing explicit (signed request, authorization
87
        // code, etc.) was present to shadow it (or we saw a code in $_REQUEST,
88
        // but it's the same as what's in the persistent store)
89
        return $storage->get('access_token', null);
90
    }
91
92
    /**
93
     * Retrieves an access token for the given authorization code
94
     * (previously generated from www.linkedin.com on behalf of
95
     * a specific user). The authorization code is sent to www.linkedin.com
96
     * and a legitimate access token is generated provided the access token
97
     * and the user for which it was generated all match, and the user is
98
     * either logged in to LinkedIn or has granted an offline access permission.
99
     *
100
     * @param UrlGeneratorInterface $urlGenerator
101
     * @param string                $code         An authorization code.
102
     *
103
     * @return AccessToken|null An access token exchanged for the authorization code, or
104
     *                          null if an access token could not be generated.
105
     */
106
    protected function getAccessTokenFromCode(UrlGeneratorInterface $urlGenerator, $code)
107
    {
108
        if (empty($code)) {
109
            return;
110
        }
111
112
        $redirectUri = $this->getStorage()->get('redirect_url');
113
        try {
114
            $url = $urlGenerator->getUrl('www', 'uas/oauth2/accessToken');
115
            $headers = ['Content-Type' => 'application/x-www-form-urlencoded'];
116
            $body = http_build_query(
117
                [
118
                    'grant_type' => 'authorization_code',
119
                    'code' => $code,
120
                    'redirect_uri' => $redirectUri,
121
                    'client_id' => $this->appId,
122
                    'client_secret' => $this->appSecret,
123
                ]
124
            );
125
126
            $response = ResponseConverter::convertToArray($this->getRequestManager()->sendRequest('POST', $url, $headers, $body));
0 ignored issues
show
Documentation introduced by
$body is of type string, but the function expects a null.

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...
127
        } catch (LinkedInApiException $e) {
128
            // most likely that user very recently revoked authorization.
129
            // In any event, we don't have an access token, so say so.
130
            return;
131
        }
132
133
        if (empty($response)) {
134
            return;
135
        }
136
137
        $tokenData = array_merge(array('access_token' => null, 'expires_in' => null), $response);
138
        $token = new AccessToken($tokenData['access_token'], $tokenData['expires_in']);
139
140
        if (!$token->hasToken()) {
141
            return;
142
        }
143
144
        return $token;
145
    }
146
147
    /**
148
     * Get a Login URL for use with redirects. By default, full page redirect is
149
     * assumed. If you are using the generated URL with a window.open() call in
150
     * JavaScript, you can pass in display=popup as part of the $params.
151
     *
152
     * The parameters:
153
     * - redirect_uri: the url to go to after a successful login
154
     * - scope: comma (or space) separated list of requested extended permissions
155
     *
156
     * @param UrlGeneratorInterface $urlGenerator
157
     * @param array                 $options      Provide custom parameters
158
     *
159
     * @return string The URL for the login flow
160
     */
161
    public function getLoginUrl(UrlGeneratorInterface $urlGenerator, $options = array())
162
    {
163
        // Generate a state
164
        $this->establishCSRFTokenState();
165
166
        // Build request params
167
        $requestParams = array(
168
            'response_type' => 'code',
169
            'client_id' => $this->appId,
170
            'state' => $this->getState(),
171
        );
172
173
        // Look for the redirect URL
174
        if (isset($options['redirect_uri'])) {
175
            $requestParams['redirect_uri'] = $options['redirect_uri'];
176
        } else {
177
            $requestParams['redirect_uri'] = $urlGenerator->getCurrentUrl();
178
        }
179
180
        // Save the redirect url for later
181
        $this->getStorage()->set('redirect_url', $requestParams['redirect_uri']);
182
183
        // if 'scope' is passed as an array, convert to space separated list
184
        $scopeParams = isset($options['scope']) ? $options['scope'] : null;
185
        if ($scopeParams) {
186
            //if scope is an array
187
            if (is_array($scopeParams)) {
188
                $requestParams['scope'] = implode(' ', $scopeParams);
189
            } elseif (is_string($scopeParams)) {
190
                //if scope is a string with ',' => make it to an array
191
                $requestParams['scope'] = str_replace(',', ' ', $scopeParams);
192
            }
193
        }
194
195
        return $urlGenerator->getUrl(
196
            'www',
197
            'uas/oauth2/authorization',
198
            $requestParams
199
        );
200
    }
201
202
    /**
203
     * Get the authorization code from the query parameters, if it exists,
204
     * and otherwise return null to signal no authorization code was
205
     * discoverable.
206
     *
207
     * @return string|null The authorization code, or null if the authorization code could not be determined.
208
     *
209
     * @throws LinkedInApiException
210
     */
211
    protected function getCode()
212
    {
213
        $storage = $this->getStorage();
214
215
        if (GlobalVariableGetter::has('code')) {
216
            if ($storage->get('code') === GlobalVariableGetter::get('code')) {
217
                //we have already validated this code
218
                return;
219
            }
220
221
            //if stored state does not exists
222
            if (null === $state = $this->getState()) {
223
                throw new LinkedInApiException('Could not find a stored CSRF state token.');
224
            }
225
226
            //if state exists in the request
227
            if (!GlobalVariableGetter::has('state')) {
228
                throw new LinkedInApiException('Could not find a CSRF state token in the request.');
229
            }
230
231
            //if state exists in session and in request and if they are not equal
232
            if ($state !== GlobalVariableGetter::get('state')) {
233
                throw new LinkedInApiException('The CSRF state token from the request does not match the stored token.');
234
            }
235
236
            // CSRF state has done its job, so clear it
237
            $this->setState(null);
238
            $storage->clear('state');
239
240
            return GlobalVariableGetter::get('code');
241
        }
242
243
        return;
244
    }
245
246
    /**
247
     * Lays down a CSRF state token for this process.
248
     */
249
    protected function establishCSRFTokenState()
250
    {
251
        if ($this->getState() === null) {
252
            $this->setState(md5(uniqid(mt_rand(), true)));
253
            $this->getStorage()->set('state', $this->getState());
254
        }
255
    }
256
257
    /**
258
     * Clear the storage.
259
     *
260
     * @return $this
261
     */
262
    public function clearStorage()
263
    {
264
        $this->getStorage()->clearAll();
265
266
        return $this;
267
    }
268
269
    /**
270
     * Get the state, use this to verify the CSRF token.
271
     *
272
     *
273
     * @return string|null
274
     */
275
    protected function getState()
276
    {
277
        if ($this->state === null) {
278
            $this->state = $this->getStorage()->get('state', null);
279
        }
280
281
        return $this->state;
282
    }
283
284
    /**
285
     * @param $state
286
     *
287
     * @return $this
288
     */
289
    protected function setState($state)
290
    {
291
        $this->state = $state;
292
293
        return $this;
294
    }
295
296
    /**
297
     * @return DataStorageInterface
298
     */
299
    protected function getStorage()
300
    {
301
        if ($this->storage === null) {
302
            $this->storage = new SessionStorage();
303
        }
304
305
        return $this->storage;
306
    }
307
308
    /**
309
     * @param DataStorageInterface $storage
310
     *
311
     * @return $this
312
     */
313
    public function setStorage(DataStorageInterface $storage)
314
    {
315
        $this->storage = $storage;
316
317
        return $this;
318
    }
319
320
    /**
321
     * @return RequestManager
322
     */
323
    protected function getRequestManager()
324
    {
325
        return $this->requestManager;
326
    }
327
}
328