AbstractProvider::oauthRequest()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 31
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 2.0009

Importance

Changes 5
Bugs 1 Features 0
Metric Value
cc 2
eloc 22
nc 2
nop 5
dl 0
loc 31
ccs 15
cts 16
cp 0.9375
crap 2.0009
rs 9.568
c 5
b 1
f 0
1
<?php
2
/**
3
 * SocialConnect project
4
 * @author: Patsura Dmitry https://github.com/ovr <[email protected]>
5
 */
6
declare(strict_types=1);
7
8
namespace SocialConnect\OAuth1;
9
10
use Psr\Http\Message\ResponseInterface;
11
use SocialConnect\Common\Exception\Unsupported;
12
use SocialConnect\Common\HttpStack;
13
use SocialConnect\OAuth1\Exception\Unauthorized;
14
use SocialConnect\OAuth1\Exception\UnknownAuthorization;
15
use SocialConnect\OAuth1\Signature\AbstractSignatureMethod;
16
use SocialConnect\Provider\AbstractBaseProvider;
17
use SocialConnect\Provider\AccessTokenInterface;
18
use SocialConnect\Provider\Consumer;
19
use SocialConnect\Provider\Exception\InvalidAccessToken;
20
use SocialConnect\Provider\Exception\InvalidResponse;
21
use SocialConnect\OAuth1\Exception\InvalidRequestToken;
22
use SocialConnect\OAuth1\Signature\MethodHMACSHA1;
23
use SocialConnect\Provider\Session\SessionInterface;
24
25
abstract class AbstractProvider extends AbstractBaseProvider
26
{
27
    /**
28
     * @var string
29
     */
30
    protected $oauth1Version = '1.0a';
31
32
    /**
33
     * @var string
34
     */
35
    protected $requestTokenMethod = 'POST';
36
37
    /**
38
     * @var Consumer
39
     */
40
    protected $consumer;
41
42
    /**
43
     * @var Token
44
     */
45
    protected $consumerToken;
46
47
    /**
48
     * @var array
49
     */
50
    protected $scope = [];
51
52
    /**
53
     * @var AbstractSignatureMethod
54
     */
55
    protected $signature;
56
57
    /**
58
     * {@inheritDoc}
59
     */
60 32
    public function __construct(HttpStack $httpStack, SessionInterface $session, array $parameters)
61
    {
62 32
        parent::__construct($httpStack, $session, $parameters);
63
64 32
        $this->consumerToken = new Token('', '');
65 32
        $this->signature = new MethodHMACSHA1();
66
    }
67
68
    /**
69
     * @return string
70
     */
71
    abstract public function getAuthorizeUri();
72
73
    /**
74
     * @return string
75
     */
76
    abstract public function getRequestTokenUri();
77
78
    /**
79
     * @return string
80
     */
81
    abstract public function getRequestTokenAccessUri();
82
83
    /**
84
     * @return Token
85
     * @throws InvalidRequestToken
86
     * @throws InvalidResponse
87
     * @throws \Psr\Http\Client\ClientExceptionInterface
88
     */
89
    protected function requestAuthToken()
90
    {
91
        $parameters = [];
92
93
        /**
94
         * OAuth Core 1.0 Revision A: oauth_callback: An absolute URL to which the Service Provider will redirect
95
         * the User back when the Obtaining User Authorization step is completed.
96
         *
97
         * http://oauth.net/core/1.0a/#auth_step1
98
         */
99
        if ('1.0a' === $this->oauth1Version) {
100
            $parameters['oauth_callback'] = $this->getRedirectUrl();
101
        }
102
103
        $response = $this->oauthRequest(
104
            $this->getRequestTokenUri(),
105
            $this->requestTokenMethod,
106
            $parameters
107
        );
108
109
        $token = $this->parseToken($response->getBody()->getContents());
110
        $this->session->set('oauth1_request_token', $token);
111
112
        return $token;
113
    }
114
115
    /**
116
     * Parse Token from response's $body
117
     *
118
     * @param string|boolean $body
119
     * @return Token
120
     * @throws InvalidRequestToken
121
     * @throws InvalidResponse
122
     */
123
    public function parseToken($body)
124
    {
125
        if (empty($body)) {
126
            throw new InvalidResponse('Provider response with empty body');
127
        }
128
129
        parse_str($body, $token);
0 ignored issues
show
Bug introduced by
It seems like $body can also be of type boolean; however, parameter $string of parse_str() 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

129
        parse_str(/** @scrutinizer ignore-type */ $body, $token);
Loading history...
130
        if (!isset($token['oauth_token']) || !isset($token['oauth_token_secret'])) {
131
            throw new InvalidRequestToken;
132
        }
133
134
        return new Token($token['oauth_token'], $token['oauth_token_secret']);
135
    }
136
137
    /**
138
     * @param string $method
139
     * @param string $uri
140
     * @param array $query
141
     * @return string
142
     */
143 5
    public function getSignatureBaseString(string $method, string $uri, array $query): string
144
    {
145
        $parts = [
146
            $method,
147
            $uri,
148 5
            Util::buildHttpQuery(
149
                $query
150
            )
151
        ];
152
153 5
        $parts = Util::urlencodeRFC3986($parts);
154
155 5
        return implode('&', $parts);
156
    }
157
158 5
    public function authorizationHeader(array $query)
159
    {
160 5
        ksort($query);
161
162 5
        $parameters = http_build_query(
163
            $query,
164
            '',
165
            ', ',
166
            PHP_QUERY_RFC3986
167
        );
168
169 5
        return "OAuth $parameters";
170
    }
171
172 4
    public function prepareRequest(string $method, string $uri, array &$headers, array &$query, ?AccessTokenInterface $accessToken = null): void
173
    {
174 4
        $headers['Accept'] = 'application/json';
175
176 4
        $query['oauth_version'] = '1.0';
177 4
        $query['oauth_nonce'] = $this->generateState();
178 4
        $query['oauth_timestamp'] = time();
179 4
        $query['oauth_consumer_key'] = $this->consumer->getKey();
180
181 4
        if ($accessToken) {
182 3
            $query['oauth_token'] = $accessToken->getToken();
183
        }
184
185 4
        $query['oauth_signature_method'] = $this->signature->getName();
186 4
        $query['oauth_signature'] = $this->signature->buildSignature(
187 4
            $this->getSignatureBaseString($method, $uri, $query),
188 4
            $this->consumer,
189 4
            $this->consumerToken
190
        );
191
192 4
        $headers['Authorization'] = $this->authorizationHeader($query);
193
    }
194
195
    /**
196
     * @param string $uri
197
     * @param string $method
198
     * @param array $query
199
     * @param array|null $payload
200
     * @param array $headers
201
     * @return ResponseInterface
202
     * @throws InvalidResponse
203
     * @throws \Psr\Http\Client\ClientExceptionInterface
204
     * @throws \Exception
205
     */
206 1
    protected function oauthRequest($uri, $method = 'GET', array $query = [], ?array $payload = null, array $headers = []): ResponseInterface
207
    {
208 1
        $headers = array_merge([
209
            'Accept' => 'application/json'
210
        ], $headers);
211
212 1
        if ($method === 'POST') {
213
            $headers['Content-Type'] = 'application/x-www-form-urlencoded';
214
        }
215
216 1
        $query['oauth_version'] = '1.0';
217 1
        $query['oauth_nonce'] = $this->generateState();
218 1
        $query['oauth_timestamp'] = time();
219 1
        $query['oauth_consumer_key'] = $this->consumer->getKey();
220
221 1
        $query['oauth_signature_method'] = $this->signature->getName();
222 1
        $query['oauth_signature'] = $this->signature->buildSignature(
223 1
            $this->getSignatureBaseString($method, $uri, $query),
224 1
            $this->consumer,
225 1
            $this->consumerToken
226
        );
227
228 1
        $headers['Authorization'] = $this->authorizationHeader($query);
229
230 1
        return $this->executeRequest(
231 1
            $this->createRequest(
232
                $method,
233
                $uri,
234
                $query,
235
                $headers,
236
                $payload
237
            )
238
        );
239
    }
240
241
    /**
242
     * {@inheritdoc}
243
     */
244
    public function makeAuthUrl(): string
245
    {
246
        $urlParameters = $this->getAuthUrlParameters();
247
        $urlParameters['oauth_token'] = $this->requestAuthToken()->getKey();
248
249
        return $this->getAuthorizeUri() . '?' . http_build_query($urlParameters, '', '&');
250
    }
251
252
    /**
253
     * @param array $parameters
254
     * @return AccessToken
255
     * @throws InvalidAccessToken
256
     * @throws InvalidResponse
257
     * @throws UnknownAuthorization
258
     * @throws \Psr\Http\Client\ClientExceptionInterface
259
     */
260
    public function getAccessTokenByRequestParameters(array $parameters)
261
    {
262
        $token = $this->session->get('oauth1_request_token');
263
        if (!$token) {
264
            throw new UnknownAuthorization();
265
        }
266
267
        $this->session->delete('oauth1_request_token');
268
269
        if (!isset($parameters['oauth_verifier'])) {
270
            throw new Unauthorized('Unknown oauth_verifier');
271
        }
272
273
        return $this->getAccessToken($token, $parameters['oauth_verifier']);
274
    }
275
276
    /**
277
     * @param Token $token
278
     * @param string $oauthVerifier
279
     * @return AccessToken
280
     * @throws \Psr\Http\Client\ClientExceptionInterface
281
     */
282
    public function getAccessToken(Token $token, string $oauthVerifier)
283
    {
284
        $this->consumerToken = $token;
285
286
        $parameters = [
287
            'oauth_consumer_key' => $this->consumer->getKey(),
288
            'oauth_token' => $token->getKey(),
289
            'oauth_verifier' => $oauthVerifier
290
        ];
291
292
        $response = $this->oauthRequest(
293
            $this->getRequestTokenAccessUri(),
294
            $this->requestTokenMethod,
295
            $parameters
296
        );
297
298
        return $this->parseAccessToken($response->getBody()->getContents());
299
    }
300
301
    /**
302
     * Parse AccessToken from response's $body
303
     *
304
     * @param string $body
305
     * @return AccessToken
306
     * @throws InvalidAccessToken
307
     * @throws InvalidResponse
308
     */
309
    public function parseAccessToken(string $body)
310
    {
311
        if (empty($body)) {
312
            throw new InvalidResponse('Provider response with empty body');
313
        }
314
315
        parse_str($body, $token);
316
317
        if ($token) {
318
            return new AccessToken($token);
319
        }
320
321
        throw new InvalidAccessToken('Server response with not valid/empty JSON');
322
    }
323
324
    /**
325
     * {@inheritDoc}
326
     */
327
    public function createAccessToken(array $information)
328
    {
329
        throw new Unsupported('It\'s usefull to use this method for OAuth1, are you sure?');
330
    }
331
332
    /**
333
     * @return array
334
     */
335
    public function getScope()
336
    {
337
        return $this->scope;
338
    }
339
340
    /**
341
     * @param array $scope
342
     */
343 32
    public function setScope(array $scope)
344
    {
345 32
        $this->scope = $scope;
346
    }
347
348
    /**
349
     * @param Token $token
350
     */
351
    public function setConsumerToken(Token $token)
352
    {
353
        $this->consumerToken = $token;
354
    }
355
356
    /**
357
     * @return \SocialConnect\OAuth1\Token
358
     */
359
    public function getConsumerToken()
360
    {
361
        return $this->consumerToken;
362
    }
363
}
364