Passed
Pull Request — master (#32)
by Anton
02:44
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 1
    public function buildAuthUrl(
77
        ServerRequestInterface $incomingRequest,
78
        array $params = []
79
    ): string {
80 1
        $requestToken = $this->fetchRequestToken($incomingRequest);
81 1
        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 1
        $params['oauth_token'] = $requestToken->getToken();
88
89 1
        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 1
    public function fetchRequestToken(ServerRequestInterface $incomingRequest, array $params = []): OAuthToken
103
    {
104 1
        $this->setAccessToken(null);
105
        $defaultParams = [
106 1
            'oauth_consumer_key' => $this->consumerKey,
107 1
            'oauth_callback' => $this->getReturnUrl($incomingRequest),
108
            //'xoauth_displayname' => Yii::getApp()->name,
109
        ];
110 1
        if (!empty($this->getScope())) {
111
            $defaultParams['scope'] = $this->getScope();
112
        }
113
114 1
        $request = $this->createRequest(
115 1
            $this->requestTokenMethod,
116 1
            $this->requestTokenUrl . '?' . http_build_query(
117 1
                array_merge($defaultParams, $params)
118
            )
119
        );
120
121 1
        $request = $this->signRequest($request);
122 1
        $response = $this->sendRequest($request);
123
124 1
        $content = Json::decode((string) $response->getBody());
125 1
        $tokenConfig = $content ?: [];
126 1
        $token = $this->createToken($tokenConfig);
127 1
        $this->setState('requestToken', $token);
128
129 1
        return $token;
130
    }
131
132
    /**
133
     * Sign given request with {@see signatureMethod}.
134
     *
135
     * @param RequestInterface $request request instance.
136
     * @param OAuthToken|null $token OAuth token to be used for signature, if not set {@see accessToken} will be used.
137
     *
138
     * @return RequestInterface
139
     */
140 3
    public function signRequest(RequestInterface $request, ?OAuthToken $token = null): RequestInterface
141
    {
142 3
        $params = RequestUtil::getParams($request);
143
144 3
        if (isset($params['oauth_signature_method']) || $request->hasHeader('authorization')) {
145
            // avoid double sign of request
146
            return $request;
147
        }
148
149 3
        if (empty($request->getUri()->getQuery())) {
150 1
            $params = $this->generateCommonRequestParams();
151
        } else {
152 2
            $params = array_merge($this->generateCommonRequestParams(), $params);
153
        }
154
155 3
        $url = (string)$request->getUri();
156
157 3
        $signatureMethod = $this->getSignatureMethod();
158
159 3
        $params['oauth_signature_method'] = $signatureMethod->getName();
160 3
        $signatureBaseString = $this->composeSignatureBaseString($request->getMethod(), $url, $params);
161 3
        $signatureKey = $this->composeSignatureKey($token);
162 3
        $params['oauth_signature'] = $signatureMethod->generateSignature($signatureBaseString, $signatureKey);
163
164
        if (
165 3
            $this->authorizationHeaderMethods === null || in_array(
166 3
                strtoupper($request->getMethod()),
167 3
                array_map(
168 3
                    'strtoupper',
169 3
                    $this->authorizationHeaderMethods
170
                ),
171 3
                true
172
            )
173
        ) {
174 1
            $authorizationHeader = $this->composeAuthorizationHeader($params);
175 1
            if (!empty($authorizationHeader)) {
176 1
                foreach ($authorizationHeader as $name => $value) {
177 1
                    $request = $request->withHeader($name, $value);
178
                }
179
180
                // removing authorization header params, avoiding duplicate param server error :
181 1
                foreach ($params as $key => $value) {
182 1
                    if (substr_compare($key, 'oauth', 0, 5) === 0) {
183 1
                        unset($params[$key]);
184
                    }
185
                }
186
            }
187
        }
188
189 3
        $uri = $request->getUri()->withQuery(http_build_query($params));
190 3
        return $request->withUri($uri);
191
    }
192
193
    /**
194
     * Generate common request params like version, timestamp etc.
195
     *
196
     * @return array common request params.
197
     */
198 3
    protected function generateCommonRequestParams(): array
199
    {
200
        return [
201 3
            'oauth_version' => self::PROTOCOL_VERSION,
202 3
            'oauth_nonce' => $this->generateNonce(),
203 3
            'oauth_timestamp' => $this->generateTimestamp(),
204
        ];
205
    }
206
207
    /**
208
     * Generates nonce value.
209
     *
210
     * @return string nonce value.
211
     */
212 3
    protected function generateNonce(): string
213
    {
214 3
        return md5(microtime() . mt_rand());
215
    }
216
217
    /**
218
     * Generates timestamp.
219
     *
220
     * @return int timestamp.
221
     */
222 3
    protected function generateTimestamp(): int
223
    {
224 3
        return time();
225
    }
226
227
    /**
228
     * Creates signature base string, which will be signed by {@see signatureMethod}.
229
     *
230
     * @param string $method request method.
231
     * @param string $url request URL.
232
     * @param array $params request params.
233
     *
234
     * @return string base signature string.
235
     */
236 3
    protected function composeSignatureBaseString($method, $url, array $params)
237
    {
238 3
        if (strpos($url, '?') !== false) {
239 2
            [$url, $queryString] = explode('?', $url, 2);
240 2
            parse_str($queryString, $urlParams);
241 2
            $params = array_merge($urlParams, $params);
242
        }
243 3
        unset($params['oauth_signature']);
244 3
        uksort(
245 3
            $params,
246 3
            'strcmp'
247
        ); // Parameters are sorted by name, using lexicographical byte value ordering. Ref: Spec: 9.1.1
248
        $parts = [
249 3
            strtoupper($method),
250 3
            $url,
251 3
            http_build_query($params, '', '&', PHP_QUERY_RFC3986),
252
        ];
253 3
        $parts = array_map('rawurlencode', $parts);
254
255 3
        return implode('&', $parts);
256
    }
257
258
    /**
259
     * Composes request signature key.
260
     *
261
     * @param OAuthToken|null $token OAuth token to be used for signature key.
262
     *
263
     * @return string signature key.
264
     */
265 3
    protected function composeSignatureKey($token = null): string
266
    {
267
        $signatureKeyParts = [
268 3
            $this->consumerSecret,
269
        ];
270
271 3
        if ($token === null) {
272 3
            $token = $this->getAccessToken();
273
        }
274 3
        if (is_object($token)) {
275
            $signatureKeyParts[] = $token->getTokenSecret();
276
        } else {
277 3
            $signatureKeyParts[] = '';
278
        }
279
280 3
        $signatureKeyParts = array_map('rawurlencode', $signatureKeyParts);
281
282 3
        return implode('&', $signatureKeyParts);
283
    }
284
285
    /**
286
     * Composes authorization header.
287
     *
288
     * @param array $params request params.
289
     * @param string $realm authorization realm.
290
     *
291
     * @return array authorization header in format: [name => content].
292
     */
293 4
    public function composeAuthorizationHeader(array $params, $realm = '')
294
    {
295 4
        $header = 'OAuth';
296 4
        $headerParams = [];
297 4
        if (!empty($realm)) {
298 1
            $headerParams[] = 'realm="' . rawurlencode($realm) . '"';
299
        }
300 4
        foreach ($params as $key => $value) {
301 4
            if (substr_compare($key, 'oauth', 0, 5)) {
302 1
                continue;
303
            }
304 4
            $headerParams[] = rawurlencode((string)$key) . '="' . rawurlencode((string)$value) . '"';
305
        }
306 4
        if (!empty($headerParams)) {
307 4
            $header .= ' ' . implode(', ', $headerParams);
308
        }
309
310 4
        return ['Authorization' => $header];
311
    }
312
313
    /**
314
     * Fetches OAuth access token.
315
     *
316
     * @param ServerRequestInterface $incomingRequest
317
     * @param string $oauthToken OAuth token returned with redirection back to client.
318
     * @param OAuthToken $requestToken OAuth request token.
319
     * @param string $oauthVerifier OAuth verifier.
320
     * @param array $params additional request params.
321
     *
322
     * @return OAuthToken OAuth access token.
323
     */
324
    public function fetchAccessToken(
325
        ServerRequestInterface $incomingRequest,
326
        string $oauthToken = null,
327
        OAuthToken $requestToken = null,
328
        string $oauthVerifier = null,
329
        array $params = []
330
    ): OAuthToken {
331
        $queryParams = $incomingRequest->getQueryParams();
332
        $bodyParams = $incomingRequest->getParsedBody();
333
        if ($oauthToken === null) {
334
            $oauthToken = $queryParams['oauth_token'] ?? $bodyParams['oauth_token'] ?? null;
335
        }
336
337
        if (!is_object($requestToken)) {
338
            $requestToken = $this->getState('requestToken');
339
            if (!is_object($requestToken)) {
340
                throw new InvalidArgumentException('Request token is required to fetch access token!');
341
            }
342
        }
343
344
        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

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