Passed
Push — master ( 13425d...f357db )
by Alexander
02:26
created

OAuth1::signRequest()   B

Complexity

Conditions 10
Paths 7

Size

Total Lines 51
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 27
CRAP Score 10.0045

Importance

Changes 0
Metric Value
cc 10
eloc 29
c 0
b 0
f 0
nc 7
nop 2
dl 0
loc 51
ccs 27
cts 28
cp 0.9643
crap 10.0045
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\AuthClient;
6
7
use InvalidArgumentException;
8
use Psr\Http\Message\RequestInterface;
9
use Psr\Http\Message\ServerRequestInterface;
10
use Yiisoft\Json\Json;
11
12
/**
13
 * OAuth1 serves as a client for the OAuth 1/1.0a flow.
14
 *
15
 * In order to acquire access token perform following sequence:
16
 *
17
 * ```php
18
 * use Yiisoft\Yii\AuthClient\OAuth1;
19
 *
20
 * // assuming class MyAuthClient extends OAuth1
21
 * $oauthClient = new MyAuthClient();
22
 * $requestToken = $oauthClient->fetchRequestToken(); // Get request token
23
 * $url = $oauthClient->buildAuthUrl($requestToken); // Get authorization URL
24
 * return Yii::getApp()->getResponse()->redirect($url); // Redirect to authorization URL
25
 *
26
 * // After user returns at our site:
27
 * $accessToken = $oauthClient->fetchAccessToken(Yii::getApp()->request->get('oauth_token'), $requestToken); // Upgrade to access token
28
 * ```
29
 *
30
 * @see https://oauth.net/1/
31
 * @see https://tools.ietf.org/html/rfc5849
32
 */
33
abstract class OAuth1 extends OAuth
34
{
35
    private const PROTOCOL_VERSION = '1.0';
36
37
    /**
38
     * @var string OAuth consumer key.
39
     */
40
    protected string $consumerKey = '';
41
    /**
42
     * @var string OAuth consumer secret.
43
     */
44
    protected string $consumerSecret = '';
45
    /**
46
     * @var string OAuth request token URL.
47
     */
48
    protected string $requestTokenUrl;
49
    /**
50
     * @var string request token HTTP method.
51
     */
52
    protected string $requestTokenMethod = 'GET';
53
    /**
54
     * @var string OAuth access token URL.
55
     */
56
    protected string $accessTokenUrl;
57
    /**
58
     * @var string access token HTTP method.
59
     */
60
    protected string $accessTokenMethod = 'GET';
61
    /**
62
     * @var array|null list of the request methods, which require adding 'Authorization' header.
63
     * By default only POST requests will have 'Authorization' header.
64
     * You may set this option to `null` in order to make all requests to use 'Authorization' header.
65
     */
66
    protected ?array $authorizationHeaderMethods = ['POST'];
67
68
    /**
69
     * Composes user authorization URL.
70
     *
71
     * @param ServerRequestInterface $incomingRequest
72
     * @param array $params additional request params.
73
     *
74
     * @return string authorize URL
75
     */
76
    public function buildAuthUrl(
77
        ServerRequestInterface $incomingRequest,
78
        array $params = []
79
    ): string {
80
        $requestToken = $this->fetchRequestToken($incomingRequest);
81
        if (!is_object($requestToken)) {
82
            $requestToken = $this->getState('requestToken');
83
            if (!is_object($requestToken)) {
84
                throw new InvalidArgumentException('Request token is required to build authorize URL!');
85
            }
86
        }
87
        $params['oauth_token'] = $requestToken->getToken();
88
89
        return RequestUtil::composeUrl($this->authUrl, $params);
90
    }
91
92
    /**
93
     * Fetches the OAuth request token.
94
     *
95
     * @param ServerRequestInterface $incomingRequest
96
     * @param array $params additional request params.
97
     *
98
     * @throws \Yiisoft\Factory\Exceptions\InvalidConfigException
99
     *
100
     * @return OAuthToken request token.
101
     */
102
    public function fetchRequestToken(ServerRequestInterface $incomingRequest, array $params = []): OAuthToken
103
    {
104
        $this->setAccessToken(null);
105
        $defaultParams = [
106
            'oauth_consumer_key' => $this->consumerKey,
107
            'oauth_callback' => $this->getReturnUrl($incomingRequest),
108
            //'xoauth_displayname' => Yii::getApp()->name,
109
        ];
110
        if (!empty($this->getScope())) {
111
            $defaultParams['scope'] = $this->getScope();
112
        }
113
114
        $request = $this->createRequest(
115
            $this->requestTokenMethod,
116
            $this->requestTokenUrl . '?' . http_build_query(
117
                array_merge($defaultParams, $params)
118
            )
119
        );
120
121
        $request = $this->signRequest($request);
122
        $response = $this->sendRequest($request);
123
124
        $token = $this->createToken(
125
            [
126
                'setParams()' => [Json::decode($response->getBody()->getContents())],
127
            ]
128
        );
129
        $this->setState('requestToken', $token);
130
131
        return $token;
132
    }
133
134
    /**
135
     * Sign given request with {@see signatureMethod}.
136
     *
137
     * @param RequestInterface $request request instance.
138
     * @param OAuthToken|null $token OAuth token to be used for signature, if not set {@see accessToken} will be used.
139
     *
140
     * @return RequestInterface
141
     */
142 2
    public function signRequest(RequestInterface $request, ?OAuthToken $token = null): RequestInterface
143
    {
144 2
        $params = RequestUtil::getParams($request);
145
146 2
        if (isset($params['oauth_signature_method']) || $request->hasHeader('authorization')) {
147
            // avoid double sign of request
148
            return $request;
149
        }
150
151 2
        if (empty($request->getUri()->getQuery())) {
152 1
            $params = $this->generateCommonRequestParams();
153
        } else {
154 1
            $params = array_merge($this->generateCommonRequestParams(), $params);
155
        }
156
157 2
        $url = (string)$request->getUri();
158
159 2
        $signatureMethod = $this->getSignatureMethod();
160
161 2
        $params['oauth_signature_method'] = $signatureMethod->getName();
162 2
        $signatureBaseString = $this->composeSignatureBaseString($request->getMethod(), $url, $params);
163 2
        $signatureKey = $this->composeSignatureKey($token);
164 2
        $params['oauth_signature'] = $signatureMethod->generateSignature($signatureBaseString, $signatureKey);
165
166
        if (
167 2
            $this->authorizationHeaderMethods === null || in_array(
168 2
                strtoupper($request->getMethod()),
169 2
                array_map(
170 2
                    'strtoupper',
171 2
                    $this->authorizationHeaderMethods
172
                ),
173 2
                true
174
            )
175
        ) {
176 1
            $authorizationHeader = $this->composeAuthorizationHeader($params);
177 1
            if (!empty($authorizationHeader)) {
178 1
                foreach ($authorizationHeader as $name => $value) {
179 1
                    $request = $request->withHeader($name, $value);
180
                }
181
182
                // removing authorization header params, avoiding duplicate param server error :
183 1
                foreach ($params as $key => $value) {
184 1
                    if (substr_compare($key, 'oauth', 0, 5) === 0) {
185 1
                        unset($params[$key]);
186
                    }
187
                }
188
            }
189
        }
190
191 2
        $uri = $request->getUri()->withQuery(http_build_query($params));
192 2
        return $request->withUri($uri);
193
    }
194
195
    /**
196
     * Generate common request params like version, timestamp etc.
197
     *
198
     * @return array common request params.
199
     */
200 2
    protected function generateCommonRequestParams(): array
201
    {
202
        return [
203 2
            'oauth_version' => self::PROTOCOL_VERSION,
204 2
            'oauth_nonce' => $this->generateNonce(),
205 2
            'oauth_timestamp' => $this->generateTimestamp(),
206
        ];
207
    }
208
209
    /**
210
     * Generates nonce value.
211
     *
212
     * @return string nonce value.
213
     */
214 2
    protected function generateNonce(): string
215
    {
216 2
        return md5(microtime() . mt_rand());
217
    }
218
219
    /**
220
     * Generates timestamp.
221
     *
222
     * @return int timestamp.
223
     */
224 2
    protected function generateTimestamp(): int
225
    {
226 2
        return time();
227
    }
228
229
    /**
230
     * Creates signature base string, which will be signed by {@see signatureMethod}.
231
     *
232
     * @param string $method request method.
233
     * @param string $url request URL.
234
     * @param array $params request params.
235
     *
236
     * @return string base signature string.
237
     */
238 2
    protected function composeSignatureBaseString($method, $url, array $params)
239
    {
240 2
        if (strpos($url, '?') !== false) {
241 1
            [$url, $queryString] = explode('?', $url, 2);
242 1
            parse_str($queryString, $urlParams);
243 1
            $params = array_merge($urlParams, $params);
244
        }
245 2
        unset($params['oauth_signature']);
246 2
        uksort(
247 2
            $params,
248 2
            'strcmp'
249
        ); // Parameters are sorted by name, using lexicographical byte value ordering. Ref: Spec: 9.1.1
250
        $parts = [
251 2
            strtoupper($method),
252 2
            $url,
253 2
            http_build_query($params, '', '&', PHP_QUERY_RFC3986),
254
        ];
255 2
        $parts = array_map('rawurlencode', $parts);
256
257 2
        return implode('&', $parts);
258
    }
259
260
    /**
261
     * Composes request signature key.
262
     *
263
     * @param OAuthToken|null $token OAuth token to be used for signature key.
264
     *
265
     * @return string signature key.
266
     */
267 2
    protected function composeSignatureKey($token = null): string
268
    {
269
        $signatureKeyParts = [
270 2
            $this->consumerSecret,
271
        ];
272
273 2
        if ($token === null) {
274 2
            $token = $this->getAccessToken();
275
        }
276 2
        if (is_object($token)) {
277
            $signatureKeyParts[] = $token->getTokenSecret();
278
        } else {
279 2
            $signatureKeyParts[] = '';
280
        }
281
282 2
        $signatureKeyParts = array_map('rawurlencode', $signatureKeyParts);
283
284 2
        return implode('&', $signatureKeyParts);
285
    }
286
287
    /**
288
     * Composes authorization header.
289
     *
290
     * @param array $params request params.
291
     * @param string $realm authorization realm.
292
     *
293
     * @return array authorization header in format: [name => content].
294
     */
295 4
    public function composeAuthorizationHeader(array $params, $realm = '')
296
    {
297 4
        $header = 'OAuth';
298 4
        $headerParams = [];
299 4
        if (!empty($realm)) {
300 1
            $headerParams[] = 'realm="' . rawurlencode($realm) . '"';
301
        }
302 4
        foreach ($params as $key => $value) {
303 4
            if (substr_compare($key, 'oauth', 0, 5)) {
304 1
                continue;
305
            }
306 4
            $headerParams[] = rawurlencode((string)$key) . '="' . rawurlencode((string)$value) . '"';
307
        }
308 4
        if (!empty($headerParams)) {
309 4
            $header .= ' ' . implode(', ', $headerParams);
310
        }
311
312 4
        return ['Authorization' => $header];
313
    }
314
315
    /**
316
     * Fetches OAuth access token.
317
     *
318
     * @param ServerRequestInterface $incomingRequest
319
     * @param string $oauthToken OAuth token returned with redirection back to client.
320
     * @param OAuthToken $requestToken OAuth request token.
321
     * @param string $oauthVerifier OAuth verifier.
322
     * @param array $params additional request params.
323
     *
324
     * @return OAuthToken OAuth access token.
325
     */
326
    public function fetchAccessToken(
327
        ServerRequestInterface $incomingRequest,
328
        string $oauthToken = null,
329
        OAuthToken $requestToken = null,
330
        string $oauthVerifier = null,
331
        array $params = []
332
    ): OAuthToken {
333
        $queryParams = $incomingRequest->getQueryParams();
334
        $bodyParams = $incomingRequest->getParsedBody();
335
        if ($oauthToken === null) {
336
            $oauthToken = $queryParams['oauth_token'] ?? $bodyParams['oauth_token'] ?? null;
337
        }
338
339
        if (!is_object($requestToken)) {
340
            $requestToken = $this->getState('requestToken');
341
            if (!is_object($requestToken)) {
342
                throw new InvalidArgumentException('Request token is required to fetch access token!');
343
            }
344
        }
345
346
        if (strcmp($requestToken->getToken(), $oauthToken) !== 0) {
0 ignored issues
show
Bug introduced by
It seems like $requestToken->getToken() can also be of type null; however, parameter $string1 of strcmp() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

346
        if (strcmp(/** @scrutinizer ignore-type */ $requestToken->getToken(), $oauthToken) !== 0) {
Loading history...
347
            throw new InvalidArgumentException('Invalid auth state parameter.');
348
        }
349
350
        $this->removeState('requestToken');
351
352
        $defaultParams = [
353
            'oauth_consumer_key' => $this->consumerKey,
354
            'oauth_token' => $requestToken->getToken(),
355
        ];
356
        if ($oauthVerifier === null) {
357
            $oauthVerifier = $queryParams['oauth_verifier'] ?? $bodyParams['oauth_verifier'];
358
        }
359
360
        if (!empty($oauthVerifier)) {
361
            $defaultParams['oauth_verifier'] = $oauthVerifier;
362
        }
363
364
        $request = $this->createRequest(
365
            $this->accessTokenMethod,
366
            RequestUtil::composeUrl($this->accessTokenUrl, array_merge($defaultParams, $params))
367
        );
368
369
        $request = $this->signRequest($request, $requestToken);
370
371
        $request = $this->signRequest($request);
372
        $response = $this->sendRequest($request);
373
374
        $token = $this->createToken(
375
            [
376
                'setParams()' => [Json::decode($response->getBody()->getContents())],
377
            ]
378
        );
379
        $this->setAccessToken($token);
380
381
        return $token;
382
    }
383
384
    public function applyAccessTokenToRequest(RequestInterface $request, OAuthToken $accessToken): RequestInterface
385
    {
386
        $data = RequestUtil::getParams($request);
387
        $data['oauth_consumer_key'] = $this->consumerKey;
388
        $data['oauth_token'] = $accessToken->getToken();
389
        return RequestUtil::addParams($request, $data);
390
    }
391
392
    /**
393
     * Gets new auth token to replace expired one.
394
     *
395
     * @param OAuthToken $token expired auth token.
396
     *
397
     * @return OAuthToken new auth token.
398
     */
399
    public function refreshAccessToken(?OAuthToken $token = null): OAuthToken
400
    {
401
        // @todo
402
        return $token;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $token could return the type null which is incompatible with the type-hinted return Yiisoft\Yii\AuthClient\OAuthToken. Consider adding an additional type-check to rule them out.
Loading history...
403
    }
404
405
    public function getConsumerKey(): string
406
    {
407
        return $this->consumerKey;
408
    }
409
410
    public function setConsumerKey(string $consumerKey): void
411
    {
412
        $this->consumerKey = $consumerKey;
413
    }
414
415
    public function getConsumerSecret(): string
416
    {
417
        return $this->consumerSecret;
418
    }
419
420
    public function setConsumerSecret(string $consumerSecret): void
421
    {
422
        $this->consumerSecret = $consumerSecret;
423
    }
424
425
    public function getRequestTokenUrl(): string
426
    {
427
        return $this->requestTokenUrl;
428
    }
429
430
    public function setRequestTokenUrl(string $requestTokenUrl): void
431
    {
432
        $this->requestTokenUrl = $requestTokenUrl;
433
    }
434
435
    public function getRequestTokenMethod(): string
436
    {
437
        return $this->requestTokenMethod;
438
    }
439
440
    public function setRequestTokenMethod(string $requestTokenMethod): void
441
    {
442
        $this->requestTokenMethod = $requestTokenMethod;
443
    }
444
445
    public function getAccessTokenUrl(): string
446
    {
447
        return $this->accessTokenUrl;
448
    }
449
450
    public function setAccessTokenUrl(string $accessTokenUrl): void
451
    {
452
        $this->accessTokenUrl = $accessTokenUrl;
453
    }
454
455
    public function getAccessTokenMethod(): string
456
    {
457
        return $this->accessTokenMethod;
458
    }
459
460
    public function setAccessTokenMethod(string $accessTokenMethod): void
461
    {
462
        $this->accessTokenMethod = $accessTokenMethod;
463
    }
464
465
    public function getAuthorizationHeaderMethods(): ?array
466
    {
467
        return $this->authorizationHeaderMethods;
468
    }
469
470 1
    public function setAuthorizationHeaderMethods(?array $authorizationHeaderMethods = null): void
471
    {
472 1
        $this->authorizationHeaderMethods = $authorizationHeaderMethods;
473 1
    }
474
475
    /**
476
     * Composes default {@see returnUrl} value.
477
     *
478
     * @return string return URL.
479
     */
480
    protected function defaultReturnUrl(ServerRequestInterface $request): string
481
    {
482
        $params = $request->getQueryParams();
483
        unset($params['oauth_token']);
484
485
        return (string)$request->getUri()->withQuery(http_build_query($params, '', '&', PHP_QUERY_RFC3986));
486
    }
487
}
488