Completed
Pull Request — master (#467)
by
unknown
04:30
created

AbstractService   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 349
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 9

Importance

Changes 0
Metric Value
wmc 36
lcom 2
cbo 9
dl 0
loc 349
rs 8.8
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
B getAuthorizationUri() 0 29 4
B requestAccessToken() 0 25 2
B __construct() 0 24 3
C request() 0 39 11
A getStorage() 0 4 1
B refreshAccessToken() 0 26 2
A isValidScope() 0 6 1
A needsStateParameterInAuthUrl() 0 4 1
A validateAuthorizationState() 0 6 2
A generateAuthorizationState() 0 4 1
A retrieveAuthorizationState() 0 4 1
A storeAuthorizationState() 0 4 1
A getExtraOAuthHeaders() 0 4 1
A getExtraApiHeaders() 0 4 1
parseAccessTokenResponse() 0 1 ?
A getAuthorizationMethod() 0 4 1
A getApiVersionString() 0 4 2
A getScopesDelimiter() 0 4 1
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_QUERY_STRING_V5 === $this->getAuthorizationMethod()) {
178
            $uri->addToQuery('token', $token->getAccessToken());
179
        } elseif (static::AUTHORIZATION_METHOD_HEADER_BEARER === $this->getAuthorizationMethod()) {
180
            $extraHeaders = array_merge(array('Authorization' => 'Bearer ' . $token->getAccessToken()), $extraHeaders);
181
        }
182
183
        $extraHeaders = array_merge($this->getExtraApiHeaders(), $extraHeaders);
184
185
        return $this->httpClient->retrieveResponse($uri, $body, $extraHeaders, $method);
186
    }
187
188
    /**
189
     * Accessor to the storage adapter to be able to retrieve tokens
190
     *
191
     * @return TokenStorageInterface
192
     */
193
    public function getStorage()
194
    {
195
        return $this->storage;
196
    }
197
198
    /**
199
     * Refreshes an OAuth2 access token.
200
     *
201
     * @param TokenInterface $token
202
     *
203
     * @return TokenInterface $token
204
     *
205
     * @throws MissingRefreshTokenException
206
     */
207
    public function refreshAccessToken(TokenInterface $token)
208
    {
209
        $refreshToken = $token->getRefreshToken();
210
211
        if (empty($refreshToken)) {
212
            throw new MissingRefreshTokenException();
213
        }
214
215
        $parameters = array(
216
            'grant_type'    => 'refresh_token',
217
            'type'          => 'web_server',
218
            'client_id'     => $this->credentials->getConsumerId(),
219
            'client_secret' => $this->credentials->getConsumerSecret(),
220
            'refresh_token' => $refreshToken,
221
        );
222
223
        $responseBody = $this->httpClient->retrieveResponse(
224
            $this->getAccessTokenEndpoint(),
225
            $parameters,
226
            $this->getExtraOAuthHeaders()
227
        );
228
        $token = $this->parseAccessTokenResponse($responseBody);
229
        $this->storage->storeAccessToken($this->service(), $token);
230
231
        return $token;
232
    }
233
234
    /**
235
     * Return whether or not the passed scope value is valid.
236
     *
237
     * @param string $scope
238
     *
239
     * @return bool
240
     */
241
    public function isValidScope($scope)
242
    {
243
        $reflectionClass = new \ReflectionClass(get_class($this));
244
245
        return in_array($scope, $reflectionClass->getConstants(), true);
246
    }
247
248
    /**
249
     * Check if the given service need to generate a unique state token to build the authorization url
250
     *
251
     * @return bool
252
     */
253
    public function needsStateParameterInAuthUrl()
254
    {
255
        return $this->stateParameterInAuthUrl;
256
    }
257
258
    /**
259
     * Validates the authorization state against a given one
260
     *
261
     * @param string $state
262
     * @throws InvalidAuthorizationStateException
263
     */
264
    protected function validateAuthorizationState($state)
265
    {
266
        if ($this->retrieveAuthorizationState() !== $state) {
267
            throw new InvalidAuthorizationStateException();
268
        }
269
    }
270
271
    /**
272
     * Generates a random string to be used as state
273
     *
274
     * @return string
275
     */
276
    protected function generateAuthorizationState()
277
    {
278
        return md5(rand());
279
    }
280
281
    /**
282
     * Retrieves the authorization state for the current service
283
     *
284
     * @return string
285
     */
286
    protected function retrieveAuthorizationState()
287
    {
288
        return $this->storage->retrieveAuthorizationState($this->service());
289
    }
290
291
    /**
292
     * Stores a given authorization state into the storage
293
     *
294
     * @param string $state
295
     */
296
    protected function storeAuthorizationState($state)
297
    {
298
        $this->storage->storeAuthorizationState($this->service(), $state);
299
    }
300
301
    /**
302
     * Return any additional headers always needed for this service implementation's OAuth calls.
303
     *
304
     * @return array
305
     */
306
    protected function getExtraOAuthHeaders()
307
    {
308
        return array();
309
    }
310
311
    /**
312
     * Return any additional headers always needed for this service implementation's API calls.
313
     *
314
     * @return array
315
     */
316
    protected function getExtraApiHeaders()
317
    {
318
        return array();
319
    }
320
321
    /**
322
     * Parses the access token response and returns a TokenInterface.
323
     *
324
     * @abstract
325
     *
326
     * @param string $responseBody
327
     *
328
     * @return TokenInterface
329
     *
330
     * @throws TokenResponseException
331
     */
332
    abstract protected function parseAccessTokenResponse($responseBody);
333
334
    /**
335
     * Returns a class constant from ServiceInterface defining the authorization method used for the API
336
     * Header is the sane default.
337
     *
338
     * @return int
339
     */
340
    protected function getAuthorizationMethod()
341
    {
342
        return static::AUTHORIZATION_METHOD_HEADER_OAUTH;
343
    }
344
345
    /**
346
     * Returns api version string if is set else retrun empty string
347
     *
348
     * @return string
349
     */
350
    protected function getApiVersionString()
351
    {
352
        return !(empty($this->apiVersion)) ? "/".$this->apiVersion : "" ;
353
    }
354
355
    /**
356
     * Returns delimiter to scopes in getAuthorizationUri
357
     * For services that do not fully respect the Oauth's RFC,
358
     * and use scopes with commas as delimiter
359
     *
360
     * @return string
361
     */
362
    protected function getScopesDelimiter()
363
    {
364
        return ' ';
365
    }
366
}
367