Completed
Pull Request — master (#471)
by
unknown
02:46
created

AbstractService   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 386
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

Changes 7
Bugs 3 Features 3
Metric Value
wmc 35
c 7
b 3
f 3
lcom 1
cbo 9
dl 0
loc 386
rs 9

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 14 1
A requestRequestToken() 0 12 1
A getAuthorizationUri() 0 10 2
B requestAccessToken() 0 30 2
A refreshAccessToken() 0 3 1
A request() 0 14 1
A getExtraOAuthHeaders() 0 4 1
A getExtraApiHeaders() 0 4 1
A buildAuthorizationHeaderForTokenRequest() 0 20 2
A buildAuthorizationHeaderForAccessRequest() 0 11 1
C buildAuthorizationHeaderForAPIRequest() 0 40 7
A getBasicAuthorizationHeaderInfo() 0 14 1
A generateNonce() 0 12 2
A getSignatureMethod() 0 4 1
A getVersion() 0 4 1
A parseRequestTokenResponse() 0 21 3
A parseAccessTokenResponse() 0 15 1
B validateTokenResponse() 0 14 6
1
<?php
2
3
namespace OAuth\OAuth1\Service;
4
5
use OAuth\Common\Consumer\CredentialsInterface;
6
use OAuth\Common\Storage\TokenStorageInterface;
7
use OAuth\Common\Http\Exception\TokenResponseException;
8
use OAuth\Common\Http\Client\ClientInterface;
9
use OAuth\Common\Http\Uri\UriInterface;
10
use OAuth\OAuth1\Signature\SignatureInterface;
11
use OAuth\OAuth1\Token\TokenInterface;
12
use OAuth\OAuth1\Token\StdOAuth1Token;
13
use OAuth\Common\Service\AbstractService as BaseAbstractService;
14
use OAuth\Common\Storage\Exception\TokenNotFoundException;
15
16
abstract class AbstractService extends BaseAbstractService implements ServiceInterface
17
{
18
    /** @const OAUTH_VERSION */
19
    const OAUTH_VERSION = 1;
20
21
    /** @var SignatureInterface */
22
    protected $signature;
23
24
    /** @var UriInterface|null */
25
    protected $baseApiUri;
26
27
    /**
28
     * {@inheritDoc}
29
     */
30
    public function __construct(
31
        CredentialsInterface $credentials,
32
        ClientInterface $httpClient,
33
        TokenStorageInterface $storage,
34
        SignatureInterface $signature,
35
        UriInterface $baseApiUri = null
36
    ) {
37
        parent::__construct($credentials, $httpClient, $storage);
38
39
        $this->signature = $signature;
40
        $this->baseApiUri = $baseApiUri;
41
42
        $this->signature->setHashingAlgorithm($this->getSignatureMethod());
43
    }
44
45
    /**
46
     * {@inheritDoc}
47
     */
48
    public function requestRequestToken()
49
    {
50
        $authorizationHeader = array('Authorization' => $this->buildAuthorizationHeaderForTokenRequest());
51
        $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders());
52
53
        $responseBody = $this->httpClient->retrieveResponse($this->getRequestTokenEndpoint(), array(), $headers);
54
55
        $token = $this->parseRequestTokenResponse($responseBody);
56
        $this->storage->storeAccessToken($this->service(), $token);
57
58
        return $token;
59
    }
60
61
    /**
62
     * {@inheritdoc}
63
     */
64
    public function getAuthorizationUri(array $additionalParameters = array())
65
    {
66
        // Build the url
67
        $url = clone $this->getAuthorizationEndpoint();
68
        foreach ($additionalParameters as $key => $val) {
69
            $url->addToQuery($key, $val);
70
        }
71
72
        return $url;
73
    }
74
75
    /**
76
     * {@inheritDoc}
77
     */
78
    public function requestAccessToken($token, $verifier, $tokenSecret = null)
79
    {
80
        if (is_null($tokenSecret)) {
81
            $storedRequestToken = $this->storage->retrieveAccessToken($this->service());
82
            $tokenSecret = $storedRequestToken->getRequestTokenSecret();
83
        }
84
        $this->signature->setTokenSecret($tokenSecret);
85
86
        $bodyParams = array(
87
            'oauth_verifier' => $verifier,
88
        );
89
90
        $authorizationHeader = array(
91
            'Authorization' => $this->buildAuthorizationHeaderForAccessRequest(
92
                'POST',
93
                $this->getAccessTokenEndpoint(),
94
                $this->storage->retrieveAccessToken($this->service()),
0 ignored issues
show
Compatibility introduced by
$this->storage->retrieve...Token($this->service()) of type object<OAuth\Common\Token\TokenInterface> is not a sub-type of object<OAuth\OAuth1\Token\TokenInterface>. It seems like you assume a child interface of the interface OAuth\Common\Token\TokenInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
95
                $bodyParams
96
            )
97
        );
98
99
        $headers = array_merge($authorizationHeader, $this->getExtraOAuthHeaders());
100
101
        $responseBody = $this->httpClient->retrieveResponse($this->getAccessTokenEndpoint(), $bodyParams, $headers);
102
103
        $token = $this->parseAccessTokenResponse($responseBody);
104
        $this->storage->storeAccessToken($this->service(), $token);
105
106
        return $token;
107
    }
108
109
    /**
110
     * Refreshes an OAuth1 access token
111
     * @param  TokenInterface $token
112
     * @return TokenInterface $token
113
     */
114
    public function refreshAccessToken(TokenInterface $token)
115
    {
116
    }
117
118
    /**
119
     * Sends an authenticated API request to the path provided.
120
     * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used.
121
     *
122
     * @param string|UriInterface $path
123
     * @param string              $method       HTTP method
124
     * @param array               $body         Request body if applicable (key/value pairs)
125
     * @param array               $extraHeaders Extra headers if applicable.
126
     *                                          These will override service-specific any defaults.
127
     *
128
     * @return string
129
     */
130
    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
131
    {
132
        $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri);
133
134
        /** @var $token StdOAuth1Token */
135
        $token = $this->storage->retrieveAccessToken($this->service());
136
        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
137
        $authorizationHeader = array(
138
            'Authorization' => $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $body)
139
        );
140
        $headers = array_merge($authorizationHeader, $extraHeaders);
141
142
        return $this->httpClient->retrieveResponse($uri, $body, $headers, $method);
143
    }
144
145
    /**
146
     * Return any additional headers always needed for this service implementation's OAuth calls.
147
     *
148
     * @return array
149
     */
150
    protected function getExtraOAuthHeaders()
151
    {
152
        return array();
153
    }
154
155
    /**
156
     * Return any additional headers always needed for this service implementation's API calls.
157
     *
158
     * @return array
159
     */
160
    protected function getExtraApiHeaders()
161
    {
162
        return array();
163
    }
164
165
    /**
166
     * Builds the authorization header for getting an access or request token.
167
     *
168
     * @param array $extraParameters
169
     *
170
     * @return string
171
     */
172
    protected function buildAuthorizationHeaderForTokenRequest(array $extraParameters = array())
173
    {
174
        $parameters = $this->getBasicAuthorizationHeaderInfo();
175
        $parameters = array_merge($parameters, $extraParameters);
176
        $parameters['oauth_signature'] = $this->signature->getSignature(
177
            $this->getRequestTokenEndpoint(),
178
            $parameters,
179
            'POST'
180
        );
181
182
        $authorizationHeader = 'OAuth ';
183
        $delimiter = '';
184
        foreach ($parameters as $key => $value) {
185
            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
186
187
            $delimiter = ', ';
188
        }
189
190
        return $authorizationHeader;
191
    }
192
    
193
    /**
194
     * Builds the authorization header to get an access token.
195
     *
196
     * @param string         $method
197
     * @param UriInterface   $uri        The uri the request is headed
198
     * @param TokenInterface $token
199
     * @param array          $bodyParams Request body if applicable (key/value pairs)
200
     *
201
     * @return string
202
     */
203
    protected function buildAuthorizationHeaderForAccessRequest(
204
        $method,
205
        UriInterface $uri,
206
        TokenInterface $token,
207
        $bodyParams = null
208
    ) {
209
        $token->setAccessToken($token->getRequestToken());
210
        $token->setAccessTokenSecret($token->getRequestTokenSecret());
211
        
212
        return $this->buildAuthorizationHeaderForAPIRequest($method, $uri, $token, $bodyParams);
213
    }
214
215
    /**
216
     * Builds the authorization header for an authenticated API request
217
     *
218
     * @param string         $method
219
     * @param UriInterface   $uri        The uri the request is headed
220
     * @param TokenInterface $token
221
     * @param array          $bodyParams Request body if applicable (key/value pairs)
222
     *
223
     * @return string
224
     */
225
    protected function buildAuthorizationHeaderForAPIRequest(
226
        $method,
227
        UriInterface $uri,
228
        TokenInterface $token,
229
        $bodyParams = null
230
    ) {
231
        /*
232
         * The storage won't send an TokenNotFoundException by itself,
233
         * even if only a request token is saved in the storage.
234
         */
235
        if (empty($token->getAccessToken())) {
236
            throw new TokenNotFoundException('No access token found.');
237
        }
238
        
239
        $this->signature->setTokenSecret($token->getAccessTokenSecret());
240
        $authParameters = $this->getBasicAuthorizationHeaderInfo();
241
        if (isset($authParameters['oauth_callback'])) {
242
            unset($authParameters['oauth_callback']);
243
        }
244
245
        $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken()));
246
247
        $signatureParams = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters;
248
        $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $signatureParams, $method);
249
250
        if (is_array($bodyParams) && isset($bodyParams['oauth_session_handle'])) {
251
            $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle'];
252
            unset($bodyParams['oauth_session_handle']);
253
        }
254
255
        $authorizationHeader = 'OAuth ';
256
        $delimiter = '';
257
258
        foreach ($authParameters as $key => $value) {
259
            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
260
            $delimiter = ', ';
261
        }
262
263
        return $authorizationHeader;
264
    }
265
266
    /**
267
     * Builds the authorization header array.
268
     *
269
     * @return array
270
     */
271
    protected function getBasicAuthorizationHeaderInfo()
272
    {
273
        $dateTime = new \DateTime();
274
        $headerParameters = array(
275
            'oauth_callback'         => $this->credentials->getCallbackUrl(),
276
            'oauth_consumer_key'     => $this->credentials->getConsumerId(),
277
            'oauth_nonce'            => $this->generateNonce(),
278
            'oauth_signature_method' => $this->getSignatureMethod(),
279
            'oauth_timestamp'        => $dateTime->format('U'),
280
            'oauth_version'          => $this->getVersion(),
281
        );
282
283
        return $headerParameters;
284
    }
285
286
    /**
287
     * Pseudo random string generator used to build a unique string to sign each request
288
     *
289
     * @param int $length
290
     *
291
     * @return string
292
     */
293
    protected function generateNonce($length = 32)
294
    {
295
        $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
296
297
        $nonce = '';
298
        $maxRand = strlen($characters)-1;
299
        for ($i = 0; $i < $length; $i++) {
300
            $nonce.= $characters[rand(0, $maxRand)];
301
        }
302
303
        return $nonce;
304
    }
305
306
    /**
307
     * @return string
308
     */
309
    protected function getSignatureMethod()
310
    {
311
        return 'HMAC-SHA1';
312
    }
313
314
    /**
315
     * This returns the version used in the authorization header of the requests
316
     *
317
     * @return string
318
     */
319
    protected function getVersion()
320
    {
321
        return '1.0';
322
    }
323
324
    /**
325
     * Parses the request token response and returns a TokenInterface.
326
     *
327
     * @param string $responseBody
328
     *
329
     * @return TokenInterface
330
     *
331
     * @throws TokenResponseException
332
     */
333
    protected function parseRequestTokenResponse($responseBody)
334
    {
335
        $data = $this->validateTokenResponse($responseBody);
336
        
337
        if (!isset($data['oauth_callback_confirmed'])
338
            || $data['oauth_callback_confirmed'] !== 'true'
339
        ) {
340
            throw new TokenResponseException('Error in retrieving token.');
341
        }
342
        
343
        $token = new StdOAuth1Token();
344
345
        $token->setRequestToken($data['oauth_token']);
346
        $token->setRequestTokenSecret($data['oauth_token_secret']);
347
348
        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
349
        unset($data['oauth_token'], $data['oauth_token_secret']);
350
        $token->setExtraParams($data);
351
352
        return $token;
353
    }
354
355
    /**
356
     * Parses the access token response and returns a TokenInterface.
357
     *
358
     * @param string $responseBody
359
     *
360
     * @return TokenInterface
361
     *
362
     * @throws TokenResponseException
363
     */
364
    protected function parseAccessTokenResponse($responseBody)
365
    {
366
        $data = $this->validateTokenResponse($responseBody);
367
        
368
        $token = new StdOAuth1Token();
369
370
        $token->setAccessToken($data['oauth_token']);
371
        $token->setAccessTokenSecret($data['oauth_token_secret']);
372
373
        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
374
        unset($data['oauth_token'], $data['oauth_token_secret']);
375
        $token->setExtraParams($data);
376
377
        return $token;
378
    }
379
    
380
    /**
381
     * General validation of the response body.
382
     * 
383
     * @param string $responseBody
384
     * @return array
385
     * @throws TokenResponseException
386
     */
387
    protected function validateTokenResponse($responseBody)
388
    {
389
        parse_str($responseBody, $data);
390
        
391
        if (null === $data || !is_array($data)) {
392
            throw new TokenResponseException('Unable to parse response: ' . $responseBody);
393
        } elseif (isset($data['error'])) {
394
            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
395
        } elseif (!isset($data["oauth_token"]) || !isset($data["oauth_token_secret"])) {
396
            throw new TokenResponseException('Invalid response. OAuth Token data not set: ' . $responseBody);
397
        }
398
        
399
        return $data;
400
    }
401
}
402