Completed
Push — master ( 29bda6...e00326 )
by David
9s
created

AbstractService::request()   D

Complexity

Conditions 10
Paths 8

Size

Total Lines 37
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
dl 0
loc 37
rs 4.8196
c 1
b 0
f 1
cc 10
eloc 25
nc 8
nop 4

How to fix   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
namespace OAuth\OAuth2\Service;
4
5
use OAuth\Common\Consumer\CredentialsInterface;
6
use OAuth\Common\Exception\Exception;
7
use OAuth\Common\Service\AbstractService as BaseAbstractService;
8
use OAuth\Common\Storage\TokenStorageInterface;
9
use OAuth\Common\Http\Exception\TokenResponseException;
10
use OAuth\Common\Http\Client\ClientInterface;
11
use OAuth\Common\Http\Uri\UriInterface;
12
use OAuth\OAuth2\Service\Exception\InvalidAuthorizationStateException;
13
use OAuth\OAuth2\Service\Exception\InvalidScopeException;
14
use OAuth\OAuth2\Service\Exception\MissingRefreshTokenException;
15
use OAuth\Common\Token\TokenInterface;
16
use OAuth\Common\Token\Exception\ExpiredTokenException;
17
18
abstract class AbstractService extends BaseAbstractService implements ServiceInterface
19
{
20
    /** @const OAUTH_VERSION */
21
    const OAUTH_VERSION = 2;
22
23
    /** @var array */
24
    protected $scopes;
25
26
    /** @var UriInterface|null */
27
    protected $baseApiUri;
28
29
    /** @var bool */
30
    protected $stateParameterInAuthUrl;
31
32
    /** @var string */
33
    protected $apiVersion;
34
35
    /**
36
     * @param CredentialsInterface  $credentials
37
     * @param ClientInterface       $httpClient
38
     * @param TokenStorageInterface $storage
39
     * @param array                 $scopes
40
     * @param UriInterface|null     $baseApiUri
41
     * @param bool $stateParameterInAutUrl
42
     * @param string                $apiVersion
43
     *
44
     * @throws InvalidScopeException
45
     */
46
    public function __construct(
47
        CredentialsInterface $credentials,
48
        ClientInterface $httpClient,
49
        TokenStorageInterface $storage,
50
        $scopes = array(),
51
        UriInterface $baseApiUri = null,
52
        $stateParameterInAutUrl = false,
53
        $apiVersion = ""
54
    ) {
55
        parent::__construct($credentials, $httpClient, $storage);
56
        $this->stateParameterInAuthUrl = $stateParameterInAutUrl;
57
58
        foreach ($scopes as $scope) {
59
            if (!$this->isValidScope($scope)) {
60
                throw new InvalidScopeException('Scope ' . $scope . ' is not valid for service ' . get_class($this));
61
            }
62
        }
63
64
        $this->scopes = $scopes;
65
66
        $this->baseApiUri = $baseApiUri;
67
68
        $this->apiVersion = $apiVersion;
69
    }
70
71
    /**
72
     * {@inheritdoc}
73
     */
74
    public function getAuthorizationUri(array $additionalParameters = array())
75
    {
76
        $parameters = array_merge(
77
            $additionalParameters,
78
            array(
79
                'type'          => 'web_server',
80
                'client_id'     => $this->credentials->getConsumerId(),
81
                'redirect_uri'  => $this->credentials->getCallbackUrl(),
82
                'response_type' => 'code',
83
            )
84
        );
85
86
        $parameters['scope'] = implode($this->getScopesDelimiter(), $this->scopes);
87
88
        if ($this->needsStateParameterInAuthUrl()) {
89
            if (!isset($parameters['state'])) {
90
                $parameters['state'] = $this->generateAuthorizationState();
91
            }
92
            $this->storeAuthorizationState($parameters['state']);
93
        }
94
95
        // Build the url
96
        $url = clone $this->getAuthorizationEndpoint();
97
        foreach ($parameters as $key => $val) {
98
            $url->addToQuery($key, $val);
99
        }
100
101
        return $url;
102
    }
103
104
    /**
105
     * {@inheritdoc}
106
     */
107
    public function requestAccessToken($code, $state = null)
108
    {
109
        if (null !== $state) {
110
            $this->validateAuthorizationState($state);
111
        }
112
113
        $bodyParams = array(
114
            'code'          => $code,
115
            'client_id'     => $this->credentials->getConsumerId(),
116
            'client_secret' => $this->credentials->getConsumerSecret(),
117
            'redirect_uri'  => $this->credentials->getCallbackUrl(),
118
            'grant_type'    => 'authorization_code',
119
        );
120
121
        $responseBody = $this->httpClient->retrieveResponse(
122
            $this->getAccessTokenEndpoint(),
123
            $bodyParams,
124
            $this->getExtraOAuthHeaders()
125
        );
126
127
        $token = $this->parseAccessTokenResponse($responseBody);
128
        $this->storage->storeAccessToken($this->service(), $token);
129
130
        return $token;
131
    }
132
133
    /**
134
     * Sends an authenticated API request to the path provided.
135
     * If the path provided is not an absolute URI, the base API Uri (must be passed into constructor) will be used.
136
     *
137
     * @param string|UriInterface $path
138
     * @param string              $method       HTTP method
139
     * @param array               $body         Request body if applicable.
140
     * @param array               $extraHeaders Extra headers if applicable. These will override service-specific
141
     *                                          any defaults.
142
     *
143
     * @return string
144
     *
145
     * @throws ExpiredTokenException
146
     * @throws Exception
147
     */
148
    public function request($path, $method = 'GET', $body = null, array $extraHeaders = array())
149
    {
150
        $uri = $this->determineRequestUriFromPath($path, $this->baseApiUri);
151
        $token = $this->storage->retrieveAccessToken($this->service());
152
153
        if ($token->getEndOfLife() !== TokenInterface::EOL_NEVER_EXPIRES
154
            && $token->getEndOfLife() !== TokenInterface::EOL_UNKNOWN
155
            && time() > $token->getEndOfLife()
156
        ) {
157
            throw new ExpiredTokenException(
158
                sprintf(
159
                    'Token expired on %s at %s',
160
                    date('m/d/Y', $token->getEndOfLife()),
161
                    date('h:i:s A', $token->getEndOfLife())
162
                )
163
            );
164
        }
165
166
        // add the token where it may be needed
167
        if (static::AUTHORIZATION_METHOD_HEADER_OAUTH === $this->getAuthorizationMethod()) {
168
            $extraHeaders = array_merge(array('Authorization' => 'OAuth ' . $token->getAccessToken()), $extraHeaders);
169
        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING === $this->getAuthorizationMethod()) {
170
            $uri->addToQuery('access_token', $token->getAccessToken());
171
        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V2 === $this->getAuthorizationMethod()) {
172
            $uri->addToQuery('oauth2_access_token', $token->getAccessToken());
173
        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V3 === $this->getAuthorizationMethod()) {
174
            $uri->addToQuery('apikey', $token->getAccessToken());
175
        } elseif (static::AUTHORIZATION_METHOD_QUERY_STRING_V4 === $this->getAuthorizationMethod()) {
176
            $uri->addToQuery('auth', $token->getAccessToken());
177
        } elseif (static::AUTHORIZATION_METHOD_HEADER_BEARER === $this->getAuthorizationMethod()) {
178
            $extraHeaders = array_merge(array('Authorization' => 'Bearer ' . $token->getAccessToken()), $extraHeaders);
179
        }
180
181
        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
182
183
        return $this->httpClient->retrieveResponse($uri, $body, $extraHeaders, $method);
184
    }
185
186
    /**
187
     * Accessor to the storage adapter to be able to retrieve tokens
188
     *
189
     * @return TokenStorageInterface
190
     */
191
    public function getStorage()
192
    {
193
        return $this->storage;
194
    }
195
196
    /**
197
     * Refreshes an OAuth2 access token.
198
     *
199
     * @param TokenInterface $token
200
     *
201
     * @return TokenInterface $token
202
     *
203
     * @throws MissingRefreshTokenException
204
     */
205
    public function refreshAccessToken(TokenInterface $token)
206
    {
207
        $refreshToken = $token->getRefreshToken();
208
209
        if (empty($refreshToken)) {
210
            throw new MissingRefreshTokenException();
211
        }
212
213
        $parameters = array(
214
            'grant_type'    => 'refresh_token',
215
            'type'          => 'web_server',
216
            'client_id'     => $this->credentials->getConsumerId(),
217
            'client_secret' => $this->credentials->getConsumerSecret(),
218
            'refresh_token' => $refreshToken,
219
        );
220
221
        $responseBody = $this->httpClient->retrieveResponse(
222
            $this->getAccessTokenEndpoint(),
223
            $parameters,
224
            $this->getExtraOAuthHeaders()
225
        );
226
        $token = $this->parseAccessTokenResponse($responseBody);
227
        $this->storage->storeAccessToken($this->service(), $token);
228
229
        return $token;
230
    }
231
232
    /**
233
     * Return whether or not the passed scope value is valid.
234
     *
235
     * @param string $scope
236
     *
237
     * @return bool
238
     */
239
    public function isValidScope($scope)
240
    {
241
        $reflectionClass = new \ReflectionClass(get_class($this));
242
243
        return in_array($scope, $reflectionClass->getConstants(), true);
244
    }
245
246
    /**
247
     * Check if the given service need to generate a unique state token to build the authorization url
248
     *
249
     * @return bool
250
     */
251
    public function needsStateParameterInAuthUrl()
252
    {
253
        return $this->stateParameterInAuthUrl;
254
    }
255
256
    /**
257
     * Validates the authorization state against a given one
258
     *
259
     * @param string $state
260
     * @throws InvalidAuthorizationStateException
261
     */
262
    protected function validateAuthorizationState($state)
263
    {
264
        if ($this->retrieveAuthorizationState() !== $state) {
265
            throw new InvalidAuthorizationStateException();
266
        }
267
    }
268
269
    /**
270
     * Generates a random string to be used as state
271
     *
272
     * @return string
273
     */
274
    protected function generateAuthorizationState()
275
    {
276
        return md5(rand());
277
    }
278
279
    /**
280
     * Retrieves the authorization state for the current service
281
     *
282
     * @return string
283
     */
284
    protected function retrieveAuthorizationState()
285
    {
286
        return $this->storage->retrieveAuthorizationState($this->service());
287
    }
288
289
    /**
290
     * Stores a given authorization state into the storage
291
     *
292
     * @param string $state
293
     */
294
    protected function storeAuthorizationState($state)
295
    {
296
        $this->storage->storeAuthorizationState($this->service(), $state);
297
    }
298
299
    /**
300
     * Return any additional headers always needed for this service implementation's OAuth calls.
301
     *
302
     * @return array
303
     */
304
    protected function getExtraOAuthHeaders()
305
    {
306
        return array();
307
    }
308
309
    /**
310
     * Return any additional headers always needed for this service implementation's API calls.
311
     *
312
     * @return array
313
     */
314
    protected function getExtraApiHeaders()
315
    {
316
        return array();
317
    }
318
319
    /**
320
     * Parses the access token response and returns a TokenInterface.
321
     *
322
     * @abstract
323
     *
324
     * @param string $responseBody
325
     *
326
     * @return TokenInterface
327
     *
328
     * @throws TokenResponseException
329
     */
330
    abstract protected function parseAccessTokenResponse($responseBody);
331
332
    /**
333
     * Returns a class constant from ServiceInterface defining the authorization method used for the API
334
     * Header is the sane default.
335
     *
336
     * @return int
337
     */
338
    protected function getAuthorizationMethod()
339
    {
340
        return static::AUTHORIZATION_METHOD_HEADER_OAUTH;
341
    }
342
343
    /**
344
     * Returns api version string if is set else retrun empty string
345
     *
346
     * @return string
347
     */
348
    protected function getApiVersionString()
349
    {
350
        return !(empty($this->apiVersion)) ? "/".$this->apiVersion : "" ;
351
    }
352
353
    /**
354
     * Returns delimiter to scopes in getAuthorizationUri
355
     * For services that do not fully respect the Oauth's RFC,
356
     * and use scopes with commas as delimiter
357
     *
358
     * @return string
359
     */
360
    protected function getScopesDelimiter()
361
    {
362
        return ' ';
363
    }
364
}
365