AbstractService   A
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 331
Duplicated Lines 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 37
eloc 102
dl 0
loc 331
c 3
b 1
f 0
rs 9.44

17 Methods

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