Passed
Push — master ( 65f9c5...2902c1 )
by Aleksei
07:53
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 BaseOAuth
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
     * Fetches the OAuth request token.
70
     *
71
     * @param ServerRequestInterface $incomingRequest
72
     * @param array $params additional request params.
73
     *
74
     * @throws \Yiisoft\Factory\Exceptions\InvalidConfigException
75
     *
76
     * @return OAuthToken request token.
77
     */
78
    public function fetchRequestToken(ServerRequestInterface $incomingRequest, array $params = [])
79
    {
80
        $this->setAccessToken(null);
81
        $defaultParams = [
82
            'oauth_consumer_key' => $this->consumerKey,
83
            'oauth_callback' => $this->getReturnUrl($incomingRequest),
84
            //'xoauth_displayname' => Yii::getApp()->name,
85
        ];
86
        if (!empty($this->getScope())) {
87
            $defaultParams['scope'] = $this->getScope();
88
        }
89
90
        $request = $this->createRequest(
91
            $this->requestTokenMethod,
92
            $this->requestTokenUrl . '?' . http_build_query(
93
                array_merge($defaultParams, $params)
94
            )
95
        );
96
97
        $request = $this->signRequest($request);
98
        $response = $this->sendRequest($request);
99
100
        $token = $this->createToken(
101
            [
102
                'setParams()' => [Json::decode($response->getBody()->getContents())],
103
            ]
104
        );
105
        $this->setState('requestToken', $token);
106
107
        return $token;
108
    }
109
110
    /**
111
     * Sign given request with {@see signatureMethod}.
112
     *
113
     * @param RequestInterface $request request instance.
114
     * @param OAuthToken|null $token OAuth token to be used for signature, if not set {@see accessToken} will be used.
115
     *
116
     * @return RequestInterface
117
     */
118 2
    public function signRequest(RequestInterface $request, ?OAuthToken $token = null): RequestInterface
119
    {
120 2
        $params = RequestUtil::getParams($request);
121
122 2
        if (isset($params['oauth_signature_method']) || $request->hasHeader('authorization')) {
123
            // avoid double sign of request
124
            return $request;
125
        }
126
127 2
        if (empty($request->getUri()->getQuery())) {
128 1
            $params = $this->generateCommonRequestParams();
129
        } else {
130 1
            $params = array_merge($this->generateCommonRequestParams(), $params);
131
        }
132
133 2
        $url = $request->getUri()->__toString();
134
135 2
        $signatureMethod = $this->getSignatureMethod();
136
137 2
        $params['oauth_signature_method'] = $signatureMethod->getName();
138 2
        $signatureBaseString = $this->composeSignatureBaseString($request->getMethod(), $url, $params);
139 2
        $signatureKey = $this->composeSignatureKey($token);
140 2
        $params['oauth_signature'] = $signatureMethod->generateSignature($signatureBaseString, $signatureKey);
141
142
        if (
143 2
            $this->authorizationHeaderMethods === null || in_array(
144 2
                strtoupper($request->getMethod()),
145 2
                array_map(
146 2
                    'strtoupper',
147 2
                    $this->authorizationHeaderMethods
148
                ),
149 2
                true
150
            )
151
        ) {
152 1
            $authorizationHeader = $this->composeAuthorizationHeader($params);
153 1
            if (!empty($authorizationHeader)) {
154 1
                foreach ($authorizationHeader as $name => $value) {
155 1
                    $request = $request->withHeader($name, $value);
156
                }
157
158
                // removing authorization header params, avoiding duplicate param server error :
159 1
                foreach ($params as $key => $value) {
160 1
                    if (substr_compare($key, 'oauth', 0, 5) === 0) {
161 1
                        unset($params[$key]);
162
                    }
163
                }
164
            }
165
        }
166
167 2
        $uri = $request->getUri()->withQuery(http_build_query($params));
168 2
        return $request->withUri($uri);
169
    }
170
171
    /**
172
     * Generate common request params like version, timestamp etc.
173
     *
174
     * @return array common request params.
175
     */
176 2
    protected function generateCommonRequestParams(): array
177
    {
178
        return [
179 2
            'oauth_version' => self::PROTOCOL_VERSION,
180 2
            'oauth_nonce' => $this->generateNonce(),
181 2
            'oauth_timestamp' => $this->generateTimestamp(),
182
        ];
183
    }
184
185
    /**
186
     * Generates nonce value.
187
     *
188
     * @return string nonce value.
189
     */
190 2
    protected function generateNonce(): string
191
    {
192 2
        return md5(microtime() . mt_rand());
193
    }
194
195
    /**
196
     * Generates timestamp.
197
     *
198
     * @return int timestamp.
199
     */
200 2
    protected function generateTimestamp(): int
201
    {
202 2
        return time();
203
    }
204
205
    /**
206
     * Creates signature base string, which will be signed by {@see signatureMethod}.
207
     *
208
     * @param string $method request method.
209
     * @param string $url request URL.
210
     * @param array $params request params.
211
     *
212
     * @return string base signature string.
213
     */
214 2
    protected function composeSignatureBaseString($method, $url, array $params)
215
    {
216 2
        if (strpos($url, '?') !== false) {
217 1
            [$url, $queryString] = explode('?', $url, 2);
218 1
            parse_str($queryString, $urlParams);
219 1
            $params = array_merge($urlParams, $params);
220
        }
221 2
        unset($params['oauth_signature']);
222 2
        uksort(
223 2
            $params,
224 2
            'strcmp'
225
        ); // Parameters are sorted by name, using lexicographical byte value ordering. Ref: Spec: 9.1.1
226
        $parts = [
227 2
            strtoupper($method),
228 2
            $url,
229 2
            http_build_query($params, '', '&', PHP_QUERY_RFC3986),
230
        ];
231 2
        $parts = array_map('rawurlencode', $parts);
232
233 2
        return implode('&', $parts);
234
    }
235
236
    /**
237
     * Composes request signature key.
238
     *
239
     * @param OAuthToken|null $token OAuth token to be used for signature key.
240
     *
241
     * @return string signature key.
242
     */
243 2
    protected function composeSignatureKey($token = null): string
244
    {
245
        $signatureKeyParts = [
246 2
            $this->consumerSecret,
247
        ];
248
249 2
        if ($token === null) {
250 2
            $token = $this->getAccessToken();
251
        }
252 2
        if (is_object($token)) {
253
            $signatureKeyParts[] = $token->getTokenSecret();
254
        } else {
255 2
            $signatureKeyParts[] = '';
256
        }
257
258 2
        $signatureKeyParts = array_map('rawurlencode', $signatureKeyParts);
259
260 2
        return implode('&', $signatureKeyParts);
261
    }
262
263
    /**
264
     * Composes authorization header.
265
     *
266
     * @param array $params request params.
267
     * @param string $realm authorization realm.
268
     *
269
     * @return array authorization header in format: [name => content].
270
     */
271 4
    public function composeAuthorizationHeader(array $params, $realm = '')
272
    {
273 4
        $header = 'OAuth';
274 4
        $headerParams = [];
275 4
        if (!empty($realm)) {
276 1
            $headerParams[] = 'realm="' . rawurlencode($realm) . '"';
277
        }
278 4
        foreach ($params as $key => $value) {
279 4
            if (substr_compare($key, 'oauth', 0, 5)) {
280 1
                continue;
281
            }
282 4
            $headerParams[] = rawurlencode((string)$key) . '="' . rawurlencode((string)$value) . '"';
283
        }
284 4
        if (!empty($headerParams)) {
285 4
            $header .= ' ' . implode(', ', $headerParams);
286
        }
287
288 4
        return ['Authorization' => $header];
289
    }
290
291
    /**
292
     * Composes user authorization URL.
293
     *
294
     * @param ServerRequestInterface $incomingRequest
295
     * @param OAuthToken $requestToken OAuth request token.
296
     * @param array $params additional request params.
297
     *
298
     * @return string authorize URL
299
     */
300 1
    public function buildAuthUrl(
301
        ?OAuthToken $requestToken = null,
302
        array $params = []
303
    ) {
304 1
        if (!is_object($requestToken)) {
305
            $requestToken = $this->getState('requestToken');
306
            if (!is_object($requestToken)) {
307
                throw new InvalidArgumentException('Request token is required to build authorize URL!');
308
            }
309
        }
310 1
        $params['oauth_token'] = $requestToken->getToken();
311
312 1
        return RequestUtil::composeUrl($this->authUrl, $params);
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
    ) {
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) {
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)
421
    {
422
        $this->consumerSecret = $consumerSecret;
423
    }
424
425
    public function getRequestTokenUrl(): string
426
    {
427
        return $this->requestTokenUrl;
428
    }
429
430 1
    public function setRequestTokenUrl(string $requestTokenUrl): void
431
    {
432 1
        $this->requestTokenUrl = $requestTokenUrl;
433 1
    }
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)
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 $request->getUri()->withQuery(http_build_query($params, '', '&', PHP_QUERY_RFC3986))->__toString();
486
    }
487
}
488