Completed
Pull Request — master (#478)
by
unknown
02:35
created

AbstractService   A

Complexity

Total Complexity 35

Size/Duplication

Total Lines 387
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 9

Importance

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

18 Methods

Rating   Name   Duplication   Size   Complexity  
A refreshAccessToken() 0 3 1
A buildAuthorizationHeaderForAccessRequest() 0 11 1
C buildAuthorizationHeaderForAPIRequest() 0 41 7
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
A __construct() 0 14 1
A requestRequestToken() 0 12 1
A getAuthorizationUri() 0 10 2
B requestAccessToken() 0 30 2
A request() 0 14 1
A getExtraOAuthHeaders() 0 4 1
A getExtraApiHeaders() 0 4 1
A buildAuthorizationHeaderForTokenRequest() 0 20 2
A getBasicAuthorizationHeaderInfo() 0 14 1
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
        $accessTokenSecret = $token->getAccessTokenSecret();
236
        if (empty($accessTokenSecret)) {
237
            throw new TokenNotFoundException('No access token found.');
238
        }
239
        
240
        $this->signature->setTokenSecret($accessTokenSecret);
241
        $authParameters = $this->getBasicAuthorizationHeaderInfo();
242
        if (isset($authParameters['oauth_callback'])) {
243
            unset($authParameters['oauth_callback']);
244
        }
245
246
        $authParameters = array_merge($authParameters, array('oauth_token' => $token->getAccessToken()));
247
248
        $signatureParams = (is_array($bodyParams)) ? array_merge($authParameters, $bodyParams) : $authParameters;
249
        $authParameters['oauth_signature'] = $this->signature->getSignature($uri, $signatureParams, $method);
250
251
        if (is_array($bodyParams) && isset($bodyParams['oauth_session_handle'])) {
252
            $authParameters['oauth_session_handle'] = $bodyParams['oauth_session_handle'];
253
            unset($bodyParams['oauth_session_handle']);
254
        }
255
256
        $authorizationHeader = 'OAuth ';
257
        $delimiter = '';
258
259
        foreach ($authParameters as $key => $value) {
260
            $authorizationHeader .= $delimiter . rawurlencode($key) . '="' . rawurlencode($value) . '"';
261
            $delimiter = ', ';
262
        }
263
264
        return $authorizationHeader;
265
    }
266
267
    /**
268
     * Builds the authorization header array.
269
     *
270
     * @return array
271
     */
272
    protected function getBasicAuthorizationHeaderInfo()
273
    {
274
        $dateTime = new \DateTime();
275
        $headerParameters = array(
276
            'oauth_callback'         => $this->credentials->getCallbackUrl(),
277
            'oauth_consumer_key'     => $this->credentials->getConsumerId(),
278
            'oauth_nonce'            => $this->generateNonce(),
279
            'oauth_signature_method' => $this->getSignatureMethod(),
280
            'oauth_timestamp'        => $dateTime->format('U'),
281
            'oauth_version'          => $this->getVersion(),
282
        );
283
284
        return $headerParameters;
285
    }
286
287
    /**
288
     * Pseudo random string generator used to build a unique string to sign each request
289
     *
290
     * @param int $length
291
     *
292
     * @return string
293
     */
294
    protected function generateNonce($length = 32)
295
    {
296
        $characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890';
297
298
        $nonce = '';
299
        $maxRand = strlen($characters)-1;
300
        for ($i = 0; $i < $length; $i++) {
301
            $nonce.= $characters[rand(0, $maxRand)];
302
        }
303
304
        return $nonce;
305
    }
306
307
    /**
308
     * @return string
309
     */
310
    protected function getSignatureMethod()
311
    {
312
        return 'HMAC-SHA1';
313
    }
314
315
    /**
316
     * This returns the version used in the authorization header of the requests
317
     *
318
     * @return string
319
     */
320
    protected function getVersion()
321
    {
322
        return '1.0';
323
    }
324
325
    /**
326
     * Parses the request token response and returns a TokenInterface.
327
     *
328
     * @param string $responseBody
329
     *
330
     * @return TokenInterface
331
     *
332
     * @throws TokenResponseException
333
     */
334
    protected function parseRequestTokenResponse($responseBody)
335
    {
336
        $data = $this->validateTokenResponse($responseBody);
337
        
338
        if (!isset($data['oauth_callback_confirmed'])
339
            || $data['oauth_callback_confirmed'] !== 'true'
340
        ) {
341
            throw new TokenResponseException('Error in retrieving token.');
342
        }
343
        
344
        $token = new StdOAuth1Token();
345
346
        $token->setRequestToken($data['oauth_token']);
347
        $token->setRequestTokenSecret($data['oauth_token_secret']);
348
349
        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
350
        unset($data['oauth_token'], $data['oauth_token_secret']);
351
        $token->setExtraParams($data);
352
353
        return $token;
354
    }
355
356
    /**
357
     * Parses the access token response and returns a TokenInterface.
358
     *
359
     * @param string $responseBody
360
     *
361
     * @return TokenInterface
362
     *
363
     * @throws TokenResponseException
364
     */
365
    protected function parseAccessTokenResponse($responseBody)
366
    {
367
        $data = $this->validateTokenResponse($responseBody);
368
        
369
        $token = new StdOAuth1Token();
370
371
        $token->setAccessToken($data['oauth_token']);
372
        $token->setAccessTokenSecret($data['oauth_token_secret']);
373
374
        $token->setEndOfLife(StdOAuth1Token::EOL_NEVER_EXPIRES);
375
        unset($data['oauth_token'], $data['oauth_token_secret']);
376
        $token->setExtraParams($data);
377
378
        return $token;
379
    }
380
    
381
    /**
382
     * General validation of the response body.
383
     * 
384
     * @param string $responseBody
385
     * @return array
386
     * @throws TokenResponseException
387
     */
388
    protected function validateTokenResponse($responseBody)
389
    {
390
        parse_str($responseBody, $data);
391
        
392
        if (null === $data || !is_array($data)) {
393
            throw new TokenResponseException('Unable to parse response: ' . $responseBody);
394
        } elseif (isset($data['error'])) {
395
            throw new TokenResponseException('Error in retrieving token: "' . $data['error'] . '"');
396
        } elseif (!isset($data["oauth_token"]) || !isset($data["oauth_token_secret"])) {
397
            throw new TokenResponseException('Invalid response. OAuth Token data not set: ' . $responseBody);
398
        }
399
        
400
        return $data;
401
    }
402
}
403