Passed
Push — master ( 67aa25...a90a56 )
by Alexander
08:11
created

BaseOAuth::defaultReturnUrl()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
rs 10
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\BaseMethod;
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 BaseOAuth extends BaseClient
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 OAuthToken|array access token instance or its array configuration.
57
     */
58
    protected $accessToken;
59
    /**
60
     * @var array|BaseMethod signature method instance or its array configuration.
61
     */
62
    protected $signatureMethod = [];
63
    private FactoryInterface $factory;
64
65
    /**
66
     * BaseOAuth constructor.
67
     * @param PsrClientInterface $httpClient
68
     * @param RequestFactoryInterface $requestFactory
69
     * @param StateStorageInterface $stateStorage
70
     * @param FactoryInterface $factory
71
     */
72 12
    public function __construct(
73
        PsrClientInterface $httpClient,
74
        RequestFactoryInterface $requestFactory,
75
        StateStorageInterface $stateStorage,
76
        FactoryInterface $factory
77
    ) {
78 12
        $this->factory = $factory;
79 12
        parent::__construct($httpClient, $requestFactory, $stateStorage);
80 12
    }
81
82
    public function getEndpoint(): string
83
    {
84
        return $this->endpoint;
85
    }
86
87 1
    public function setEndpoint(string $endpoint): void
88
    {
89 1
        $this->endpoint = $endpoint;
90 1
    }
91
92
    public function getAuthUrl(): string
93
    {
94
        return $this->authUrl;
95
    }
96
97 2
    public function setAuthUrl(string $authUrl): void
98
    {
99 2
        $this->authUrl = $authUrl;
100 2
    }
101
102
    /**
103
     * @param ServerRequestInterface $request
104
     * @return string return URL.
105
     */
106 2
    public function getReturnUrl(ServerRequestInterface $request): string
107
    {
108 2
        if ($this->returnUrl === null) {
109
            $this->returnUrl = $this->defaultReturnUrl($request);
110
        }
111 2
        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...
112
    }
113
114
    /**
115
     * @param string $returnUrl return URL
116
     */
117 2
    public function setReturnUrl(string $returnUrl): void
118
    {
119 2
        $this->returnUrl = $returnUrl;
120 2
    }
121
122
    /**
123
     * Composes default {@see returnUrl} value.
124
     * @param ServerRequestInterface $request
125
     * @return string return URL.
126
     */
127
    protected function defaultReturnUrl(ServerRequestInterface $request): string
128
    {
129
        return $request->getUri()->__toString();
130
    }
131
132
    /**
133
     * @return array|BaseMethod signature method instance.
134
     */
135 4
    public function getSignatureMethod(): BaseMethod
136
    {
137 4
        if (!is_object($this->signatureMethod)) {
138 2
            $this->signatureMethod = $this->createSignatureMethod($this->signatureMethod);
139
        }
140
141 4
        return $this->signatureMethod;
142
    }
143
144
    /**
145
     * Set signature method to be used.
146
     * @param array|BaseMethod $signatureMethod signature method instance or its array configuration.
147
     * @throws InvalidArgumentException on wrong argument.
148
     */
149 3
    public function setSignatureMethod($signatureMethod): void
150
    {
151 3
        if (!is_object($signatureMethod) && !is_array($signatureMethod)) {
0 ignored issues
show
introduced by
The condition is_array($signatureMethod) is always true.
Loading history...
152
            throw new InvalidArgumentException(
153
                '"' . get_class($this) . '::signatureMethod"'
154
                . ' should be instance of "\Yiisoft\Yii\AuthClient\Signature\BaseMethod" or its array configuration. "'
155
                . gettype($signatureMethod) . '" has been given.'
156
            );
157
        }
158 3
        $this->signatureMethod = $signatureMethod;
159 3
    }
160
161
    /**
162
     * Creates signature method instance from its configuration.
163
     * @param array $signatureMethodConfig signature method configuration.
164
     * @return object|BaseMethod signature method instance.
165
     */
166 2
    protected function createSignatureMethod(array $signatureMethodConfig): BaseMethod
167
    {
168 2
        if (!array_key_exists('__class', $signatureMethodConfig)) {
169 1
            $signatureMethodConfig['__class'] = Signature\HmacSha::class;
170 1
            $signatureMethodConfig['__construct()'] = ['sha1'];
171
        }
172 2
        return $this->factory->create($signatureMethodConfig);
173
    }
174
175
    public function withAutoRefreshAccessToken(): self
176
    {
177
        $new = clone $this;
178
        $new->autoRefreshAccessToken = true;
179
        return $new;
180
    }
181
182
    public function withoutAutoRefreshAccessToken(): self
183
    {
184
        $new = clone $this;
185
        $new->autoRefreshAccessToken = false;
186
        return $new;
187
    }
188
189
    /**
190
     * Performs request to the OAuth API returning response data.
191
     * You may use {@see createApiRequest()} method instead, gaining more control over request execution.
192
     * @param string $apiSubUrl API sub URL, which will be append to {@see apiBaseUrl}, or absolute API URL.
193
     * @param string $method request method.
194
     * @param array|string $data request data or content.
195
     * @param array $headers additional request headers.
196
     * @return array API response data.
197
     * @throws Exception
198
     * @see createApiRequest()
199
     */
200
    public function api($apiSubUrl, $method = 'GET', $data = [], $headers = []): array
201
    {
202
        $request = $this->createApiRequest($method, $apiSubUrl);
203
        $request = RequestUtil::addHeaders($request, $headers);
204
205
        if (!empty($data)) {
206
            if (is_array($data)) {
207
                $request = RequestUtil::addParams($request, $data);
208
            } else {
209
                $request->getBody()->write($data);
210
            }
211
        }
212
213
        $request = $this->beforeApiRequestSend($request);
214
        $response = $this->sendRequest($request);
215
216
        if ($response->getStatusCode() !== 200) {
217
            throw new InvalidResponseException(
218
                $response,
219
                'Request failed with code: ' . $response->getStatusCode() . ', message: ' . $response->getBody()
220
            );
221
        }
222
223
        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...
224
    }
225
226
    /**
227
     * Creates an HTTP request for the API call.
228
     * The created request will be automatically processed adding access token parameters and signature
229
     * before sending. You may use {@see createRequest()} to gain full control over request composition and execution.
230
     * @param string $method
231
     * @param string $uri
232
     * @return RequestInterface HTTP request instance.
233
     * @see createRequest()
234
     */
235 1
    public function createApiRequest(string $method, string $uri): RequestInterface
236
    {
237 1
        return $this->createRequest($method, $this->endpoint . $uri);
238
    }
239
240
    public function beforeApiRequestSend(RequestInterface $request): RequestInterface
241
    {
242
        $accessToken = $this->getAccessToken();
243
        if (!is_object($accessToken) || !$accessToken->getIsValid()) {
244
            throw new Exception('Invalid access token.');
245
        }
246
247
        return $this->applyAccessTokenToRequest($request, $accessToken);
248
    }
249
250
    /**
251
     * @return OAuthToken auth token instance.
252
     */
253 5
    public function getAccessToken(): ?OAuthToken
254
    {
255 5
        if (!is_object($this->accessToken)) {
256 3
            $this->accessToken = $this->restoreAccessToken();
257
        }
258
259 5
        return $this->accessToken;
260
    }
261
262
    /**
263
     * Sets access token to be used.
264
     * @param array|OAuthToken $token access token or its configuration.
265
     */
266 4
    public function setAccessToken($token): void
267
    {
268 4
        if (!is_object($token) && $token !== null) {
269 2
            $token = $this->createToken($token);
270
        }
271 4
        $this->accessToken = $token;
272 4
        $this->saveAccessToken($token);
273 4
    }
274
275
    /**
276
     * Creates token from its configuration.
277
     * @param array $tokenConfig token configuration.
278
     * @return object|OAuthToken
279
     * @throws \Yiisoft\Factory\Exceptions\InvalidConfigException
280
     */
281 2
    protected function createToken(array $tokenConfig = [])
282
    {
283 2
        if (!array_key_exists('__class', $tokenConfig)) {
284 2
            $tokenConfig['__class'] = OAuthToken::class;
285
        }
286 2
        return $this->factory->create($tokenConfig);
287
    }
288
289
    /**
290
     * Saves token as persistent state.
291
     * @param OAuthToken|null $token auth token to be saved.
292
     * @return $this the object itself.
293
     */
294 4
    protected function saveAccessToken($token): self
295
    {
296 4
        return $this->setState('token', $token);
297
    }
298
299
    /**
300
     * Restores access token.
301
     * @return OAuthToken auth token.
302
     */
303 3
    protected function restoreAccessToken(): ?OAuthToken
304
    {
305 3
        $token = $this->getState('token');
306 3
        if (is_object($token)) {
307
            /* @var $token OAuthToken */
308
            if ($token->getIsExpired() && $this->autoRefreshAccessToken) {
309
                $token = $this->refreshAccessToken($token);
310
            }
311
        }
312 3
        return $token;
313
    }
314
315
    /**
316
     * Gets new auth token to replace expired one.
317
     * @param OAuthToken $token expired auth token.
318
     * @return OAuthToken new auth token.
319
     */
320
    abstract public function refreshAccessToken(OAuthToken $token): OAuthToken;
321
322
    /**
323
     * Applies access token to the HTTP request instance.
324
     * @param RequestInterface $request HTTP request instance.
325
     * @param OAuthToken $accessToken access token instance.
326
     */
327
    abstract public function applyAccessTokenToRequest(
328
        RequestInterface $request,
329
        OAuthToken $accessToken
330
    ): RequestInterface;
331
332
    /**
333
     * @return string
334
     */
335 1
    public function getScope(): string
336
    {
337 1
        if ($this->scope === null) {
338 1
            return $this->getDefaultScope();
339
        }
340
341
        return $this->scope;
342
    }
343
344
    /**
345
     * @param string $scope
346
     */
347
    public function setScope(string $scope): void
348
    {
349
        $this->scope = $scope;
350
    }
351
352 1
    protected function getDefaultScope(): string
353
    {
354 1
        return '';
355
    }
356
}
357