Test Failed
Pull Request — master (#21)
by Rustam
03:07
created

OAuth::setAccessToken()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 4
nc 2
nop 1
dl 0
loc 7
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Yii\AuthClient;
6
7
use Exception;
8
use InvalidArgumentException;
9
use Psr\Http\Client\ClientInterface as PsrClientInterface;
10
use Psr\Http\Message\RequestFactoryInterface;
11
use Psr\Http\Message\RequestInterface;
12
use Psr\Http\Message\ServerRequestInterface;
13
use Psr\Http\Message\UriInterface;
14
use Yiisoft\Factory\FactoryInterface;
15
use Yiisoft\Json\Json;
16
use Yiisoft\Yii\AuthClient\Exception\InvalidResponseException;
17
use Yiisoft\Yii\AuthClient\Signature\Signature;
18
use Yiisoft\Yii\AuthClient\StateStorage\StateStorageInterface;
19
20
use function is_array;
21
use function is_object;
22
23
/**
24
 * BaseOAuth is a base class for the OAuth clients.
25
 *
26
 * @link http://oauth.net/
27
 */
28
abstract class OAuth extends AuthClient
29
{
30
    /**
31
     * @var string API base URL.
32
     * This field will be used as {@see UriInterface::getPath()}} value of {@see httpClient}.
33
     * Note: changing this property will take no effect after {@see httpClient} is instantiated.
34
     */
35
    protected string $endpoint;
36
    /**
37
     * @var string authorize URL.
38
     */
39
    protected string $authUrl;
40
    /**
41
     * @var string string auth request scope.
42
     */
43
    protected ?string $scope = null;
44
    /**
45
     * @var bool whether to automatically perform 'refresh access token' request on expired access token.
46
     */
47
    protected bool $autoRefreshAccessToken = true;
48
49
    /**
50
     * @var string URL, which user will be redirected after authentication at the OAuth provider web site.
51
     * Note: this should be absolute URL (with http:// or https:// leading).
52
     * By default current URL will be used.
53
     */
54
    protected ?string $returnUrl = null;
55
    /**
56
     * @var array|OAuthToken access token instance or its array configuration.
57
     */
58
    protected $accessToken;
59
    /**
60
     * @var array|Signature signature method instance or its array configuration.
61
     */
62
    protected $signatureMethod = [];
63
    private FactoryInterface $factory;
64
65
    /**
66
     * BaseOAuth constructor.
67
     *
68
     * @param PsrClientInterface $httpClient
69
     * @param RequestFactoryInterface $requestFactory
70
     * @param StateStorageInterface $stateStorage
71
     * @param FactoryInterface $factory
72
     */
73
    public function __construct(
74
        PsrClientInterface $httpClient,
75
        RequestFactoryInterface $requestFactory,
76
        StateStorageInterface $stateStorage,
77
        FactoryInterface $factory
78
    ) {
79
        $this->factory = $factory;
80
        parent::__construct($httpClient, $requestFactory, $stateStorage);
81
    }
82
83
    public function getEndpoint(): string
84
    {
85
        return $this->endpoint;
86
    }
87
88
    public function setEndpoint(string $endpoint): void
89
    {
90
        $this->endpoint = $endpoint;
91
    }
92
93
    public function getAuthUrl(): string
94
    {
95
        return $this->authUrl;
96
    }
97
98
    public function setAuthUrl(string $authUrl): void
99
    {
100
        $this->authUrl = $authUrl;
101
    }
102
103
    /**
104
     * @param ServerRequestInterface $request
105
     *
106
     * @return string return URL.
107
     */
108
    public function getReturnUrl(ServerRequestInterface $request): string
109
    {
110
        if ($this->returnUrl === null) {
111
            $this->returnUrl = $this->defaultReturnUrl($request);
112
        }
113
        return $this->returnUrl;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->returnUrl could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
114
    }
115
116
    /**
117
     * @param string $returnUrl return URL
118
     */
119
    public function setReturnUrl(string $returnUrl): void
120
    {
121
        $this->returnUrl = $returnUrl;
122
    }
123
124
    /**
125
     * Composes default {@see returnUrl} value.
126
     *
127
     * @param ServerRequestInterface $request
128
     *
129
     * @return string return URL.
130
     */
131
    protected function defaultReturnUrl(ServerRequestInterface $request): string
132
    {
133
        return (string)$request->getUri();
134
    }
135
136
    /**
137
     * @return array|Signature signature method instance.
138
     */
139
    public function getSignatureMethod(): Signature
140
    {
141
        if (!is_object($this->signatureMethod)) {
142
            $this->signatureMethod = $this->createSignatureMethod($this->signatureMethod);
143
        }
144
145
        return $this->signatureMethod;
146
    }
147
148
    /**
149
     * Set signature method to be used.
150
     *
151
     * @param array|Signature $signatureMethod signature method instance or its array configuration.
152
     *
153
     * @throws InvalidArgumentException on wrong argument.
154
     */
155
    public function setSignatureMethod($signatureMethod): void
156
    {
157
        if (!is_object($signatureMethod) && !is_array($signatureMethod)) {
0 ignored issues
show
introduced by
The condition is_array($signatureMethod) is always true.
Loading history...
158
            throw new InvalidArgumentException(
159
                '"' . static::class . '::signatureMethod"'
160
                . ' should be instance of "\Yiisoft\Yii\AuthClient\Signature\BaseMethod" or its array configuration. "'
161
                . gettype($signatureMethod) . '" has been given.'
162
            );
163
        }
164
        $this->signatureMethod = $signatureMethod;
165
    }
166
167
    /**
168
     * Creates signature method instance from its configuration.
169
     *
170
     * @param array $signatureMethodConfig signature method configuration.
171
     *
172
     * @return object|Signature signature method instance.
173
     */
174
    protected function createSignatureMethod(array $signatureMethodConfig): Signature
175
    {
176
        if (!array_key_exists('__class', $signatureMethodConfig)) {
177
            $signatureMethodConfig['__class'] = Signature\HmacSha::class;
0 ignored issues
show
Bug introduced by
The type Yiisoft\Yii\AuthClient\Signature\Signature\HmacSha was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
178
            $signatureMethodConfig['__construct()'] = ['sha1'];
179
        }
180
        return $this->factory->create($signatureMethodConfig);
181
    }
182
183
    public function withAutoRefreshAccessToken(): self
184
    {
185
        $new = clone $this;
186
        $new->autoRefreshAccessToken = true;
187
        return $new;
188
    }
189
190
    public function withoutAutoRefreshAccessToken(): self
191
    {
192
        $new = clone $this;
193
        $new->autoRefreshAccessToken = false;
194
        return $new;
195
    }
196
197
    /**
198
     * Performs request to the OAuth API returning response data.
199
     * You may use {@see createApiRequest()} method instead, gaining more control over request execution.
200
     *
201
     * @param string $apiSubUrl API sub URL, which will be append to {@see apiBaseUrl}, or absolute API URL.
202
     * @param string $method request method.
203
     * @param array|string $data request data or content.
204
     * @param array $headers additional request headers.
205
     *
206
     * @throws Exception
207
     *
208
     * @return array API response data.
209
     *
210
     * @see createApiRequest()
211
     */
212
    public function api($apiSubUrl, $method = 'GET', $data = [], $headers = []): array
213
    {
214
        $request = $this->createApiRequest($method, $apiSubUrl);
215
        $request = RequestUtil::addHeaders($request, $headers);
216
217
        if (!empty($data)) {
218
            if (is_array($data)) {
219
                $request = RequestUtil::addParams($request, $data);
220
            } else {
221
                $request->getBody()->write($data);
222
            }
223
        }
224
225
        $request = $this->beforeApiRequestSend($request);
226
        $response = $this->sendRequest($request);
227
228
        if ($response->getStatusCode() !== 200) {
229
            throw new InvalidResponseException(
230
                $response,
231
                'Request failed with code: ' . $response->getStatusCode() . ', message: ' . $response->getBody()
232
            );
233
        }
234
235
        return Json::decode($response->getBody()->getContents());
0 ignored issues
show
Bug Best Practice introduced by
The expression return Yiisoft\Json\Json...tBody()->getContents()) could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
236
    }
237
238
    /**
239
     * Creates an HTTP request for the API call.
240
     * The created request will be automatically processed adding access token parameters and signature
241
     * before sending. You may use {@see createRequest()} to gain full control over request composition and execution.
242
     *
243
     * @param string $method
244
     * @param string $uri
245
     *
246
     * @return RequestInterface HTTP request instance.
247
     *
248
     * @see createRequest()
249
     */
250
    public function createApiRequest(string $method, string $uri): RequestInterface
251
    {
252
        return $this->createRequest($method, $this->endpoint . $uri);
253
    }
254
255
    public function beforeApiRequestSend(RequestInterface $request): RequestInterface
256
    {
257
        $accessToken = $this->getAccessToken();
258
        if (!is_object($accessToken) || !$accessToken->getIsValid()) {
259
            throw new Exception('Invalid access token.');
260
        }
261
262
        return $this->applyAccessTokenToRequest($request, $accessToken);
263
    }
264
265
    /**
266
     * @return OAuthToken auth token instance.
267
     */
268
    public function getAccessToken(): ?OAuthToken
269
    {
270
        if (!is_object($this->accessToken)) {
271
            $this->accessToken = $this->restoreAccessToken();
272
        }
273
274
        return $this->accessToken;
275
    }
276
277
    /**
278
     * Sets access token to be used.
279
     *
280
     * @param array|OAuthToken $token access token or its configuration.
281
     */
282
    public function setAccessToken($token): void
283
    {
284
        if (!is_object($token) && $token !== null) {
285
            $token = $this->createToken($token);
286
        }
287
        $this->accessToken = $token;
288
        $this->saveAccessToken($token);
289
    }
290
291
    /**
292
     * Restores access token.
293
     *
294
     * @return OAuthToken auth token.
295
     */
296
    protected function restoreAccessToken(): ?OAuthToken
297
    {
298
        $token = $this->getState('token');
299
        if (is_object($token)) {
300
            /* @var $token OAuthToken */
301
            if ($token->getIsExpired() && $this->autoRefreshAccessToken) {
302
                $token = $this->refreshAccessToken($token);
303
            }
304
        }
305
        return $token;
306
    }
307
308
    /**
309
     * Gets new auth token to replace expired one.
310
     *
311
     * @param OAuthToken $token expired auth token.
312
     *
313
     * @return OAuthToken new auth token.
314
     */
315
    abstract public function refreshAccessToken(OAuthToken $token): OAuthToken;
316
317
    /**
318
     * Applies access token to the HTTP request instance.
319
     *
320
     * @param RequestInterface $request HTTP request instance.
321
     * @param OAuthToken $accessToken access token instance.
322
     */
323
    abstract public function applyAccessTokenToRequest(
324
        RequestInterface $request,
325
        OAuthToken $accessToken
326
    ): RequestInterface;
327
328
    /**
329
     * Creates token from its configuration.
330
     *
331
     * @param array $tokenConfig token configuration.
332
     *
333
     * @throws \Yiisoft\Factory\Exceptions\InvalidConfigException
334
     *
335
     * @return OAuthToken|object
336
     */
337
    protected function createToken(array $tokenConfig = [])
338
    {
339
        if (!array_key_exists('__class', $tokenConfig)) {
340
            $tokenConfig['__class'] = OAuthToken::class;
341
        }
342
        return $this->factory->create($tokenConfig);
343
    }
344
345
    /**
346
     * Saves token as persistent state.
347
     *
348
     * @param OAuthToken|null $token auth token to be saved.
349
     *
350
     * @return $this the object itself.
351
     */
352
    protected function saveAccessToken($token): self
353
    {
354
        return $this->setState('token', $token);
355
    }
356
357
    /**
358
     * @return string
359
     */
360
    public function getScope(): string
361
    {
362
        if ($this->scope === null) {
363
            return $this->getDefaultScope();
364
        }
365
366
        return $this->scope;
367
    }
368
369
    /**
370
     * @param string $scope
371
     */
372
    public function setScope(string $scope): void
373
    {
374
        $this->scope = $scope;
375
    }
376
377
    protected function getDefaultScope(): string
378
    {
379
        return '';
380
    }
381
}
382