Completed
Push — develop ( edb8bf...6f43a0 )
by Nate
08:26
created

Callback::persistNewToken()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 0
cts 21
cp 0
rs 9.248
c 0
b 0
f 0
cc 2
nc 2
nop 2
crap 6
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/patron/license
6
 * @link       https://www.flipboxfactory.com/software/patron/
7
 */
8
9
namespace flipbox\patron\actions\authorization;
10
11
use Craft;
12
use flipbox\craft\ember\actions\LookupTrait;
13
use flipbox\patron\events\PersistToken;
14
use flipbox\patron\helpers\ProviderHelper;
15
use flipbox\patron\Patron;
16
use flipbox\patron\queries\ProviderQuery;
17
use flipbox\patron\records\Token;
18
use League\OAuth2\Client\Provider\AbstractProvider;
19
use League\OAuth2\Client\Token\AccessToken;
20
use yii\base\Event;
21
use yii\web\HttpException;
22
23
/**
24
 * @author Flipbox Factory <[email protected]>
25
 * @since 1.0.0
26
 */
27
class Callback extends Action
28
{
29
    use LookupTrait;
30
31
    /**
32
     * @inheritdoc
33
     */
34
    public function run()
35
    {
36
        $patron = Patron::getInstance();
37
38
        // Get code
39
        if (!$code = Craft::$app->getRequest()->getParam('code')) {
40
            return $this->handleInvalidCodeResponse();
41
        }
42
43
        // Validate state
44
        $state = Craft::$app->getRequest()->getParam('state');
45
        if (!$this->isValidState($state)) {
46
            return $this->handleInvalidStateResponse($state);
47
        }
48
49
        // Get provider
50
        $identifier = $patron->getSession()->getProvider();
51
        if (!$identifier || (!$provider = $this->find($identifier))) {
52
            return $this->handleNotFoundResponse();
53
        }
54
55
        return $this->runInternal(
56
            $code,
57
            $provider
58
        );
59
    }
60
61
    /**
62
     * @param int $id
63
     * @return AbstractProvider
64
     * @throws \yii\base\InvalidConfigException
65
     */
66
    protected function find(int $id)
67
    {
68
        return (new ProviderQuery())
69
            ->id($id)
70
            ->one();
71
    }
72
73
    /**
74
     * @param $code
75
     * @param $identifier
76
     * @param AbstractProvider $provider
77
     * @return AccessToken|mixed
78
     * @throws HttpException
79
     */
80
    public function runInternal(
81
        $code,
82
        AbstractProvider $provider
83
    ) {
84
        if (($access = $this->checkAccess($provider)) !== true) {
85
            return $access;
86
        }
87
88
        return $this->performAction($code, $provider);
89
    }
90
91
    /**
92
     * @param $code
93
     * @param AbstractProvider $provider
94
     * @return AccessToken
95
     * @throws HttpException
96
     */
97
    public function performAction(
98
        $code,
99
        AbstractProvider $provider
100
    ): AccessToken {
101
102
        return $this->handleExceptions(function () use ($code, $provider) {
103
104
            // Get token via authorization code grant.
105
            $accessToken = $provider->getAccessToken(
106
                'authorization_code',
107
                [
108
                    'code' => $code
109
                ]
110
            );
111
112
            // Save token
113
            $this->persistNewToken(
114
                $accessToken,
0 ignored issues
show
Compatibility introduced by
$accessToken of type object<League\OAuth2\Cli...n\AccessTokenInterface> is not a sub-type of object<League\OAuth2\Client\Token\AccessToken>. It seems like you assume a concrete implementation of the interface League\OAuth2\Client\Token\AccessTokenInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
115
                $provider
116
            );
117
118
            return $accessToken;
119
        });
120
    }
121
122
    /**
123
     * @param AccessToken $accessToken
124
     * @param AbstractProvider $provider
125
     * @return bool
126
     * @throws \Exception
127
     */
128
    protected function persistNewToken(
129
        AccessToken $accessToken,
130
        AbstractProvider $provider
131
    ): bool {
132
133
        $record = new Token();
134
135
        $record->setAttributes(
136
            [
137
                'accessToken' => $accessToken->getToken(),
138
                'refreshToken' => $accessToken->getRefreshToken(),
139
                'providerId' => ProviderHelper::lookupId($provider),
140
                'values' => $accessToken->getValues(),
141
                'dateExpires' => $accessToken->getExpires(),
142
                'enabled' => true
143
            ]
144
        );
145
146
        $event = new PersistToken([
147
            'token' => $accessToken,
148
            'provider' => $provider,
149
            'record' => $record
150
        ]);
151
152
        Event::trigger(
153
            get_class($provider),
154
            Patron::EVENT_BEFORE_PERSIST_TOKEN,
155
            $event
156
        );
157
158
        if (!$record->save()) {
159
            return false;
160
        }
161
162
        Event::trigger(
163
            get_class($provider),
164
            Patron::EVENT_AFTER_PERSIST_TOKEN,
165
            $event
166
        );
167
168
        return true;
169
    }
170
171
    /**
172
     * @param string|null $state
173
     * @return bool
174
     */
175
    protected function isValidState(string $state = null): bool
176
    {
177
        if ($state !== Patron::getInstance()->getSession()->getState()) {
178
            return false;
179
        }
180
        return true;
181
    }
182
183
    /**
184
     * @param string|null $url
185
     * @return string
186
     */
187
    protected function resolveRedirectUrl(string $url = null)
188
    {
189
        if (empty($url)) {
190
            $url = Patron::getInstance()->getSettings()->getCallbackUrl();
191
        }
192
193
        return $url;
194
    }
195
196
197
    /*******************************************
198
     * INVALID STATE RESPONSE
199
     *******************************************/
200
201
    /**
202
     * @param string|null $state
203
     * @throws HttpException
204
     */
205
    protected function handleInvalidStateResponse(string $state = null)
206
    {
207
        Patron::warning(Craft::t(
208
            'patron',
209
            "Invalid state: '{state}'",
210
            [
211
                '{state}' => $state
212
            ]
213
        ));
214
215
        throw new HttpException(
216
            $this->statusCodeInvalidState(),
217
            $this->messageInvalidState()
218
        );
219
    }
220
221
    /**
222
     * @return string
223
     */
224
    protected function messageInvalidState(): string
225
    {
226
        return Craft::t('patron', 'Invalid state.');
227
    }
228
229
    /**
230
     * HTTP not found response code
231
     *
232
     * @return int
233
     */
234
    protected function statusCodeInvalidState(): int
235
    {
236
        return 404;
237
    }
238
239
240
    /*******************************************
241
     * USER NOT FOUND RESPONSE
242
     *******************************************/
243
244
    /**
245
     * @throws HttpException
246
     */
247
    protected function handleUserNotFoundResponse()
248
    {
249
        throw new HttpException(
250
            $this->statusCodeUserNotFound(),
251
            $this->messageUserNotFound()
252
        );
253
    }
254
255
    /**
256
     * @return string
257
     */
258
    protected function messageUserNotFound(): string
259
    {
260
        return Craft::t('patron', 'Unable to find user.');
261
    }
262
263
    /**
264
     * HTTP not found response code
265
     *
266
     * @return int
267
     */
268
    protected function statusCodeUserNotFound(): int
269
    {
270
        return 404;
271
    }
272
273
274
    /*******************************************
275
     * INVALID CODE RESPONSE
276
     *******************************************/
277
278
    /**
279
     * @throws HttpException
280
     */
281
    protected function handleInvalidCodeResponse()
282
    {
283
        throw new HttpException(
284
            $this->statusCodeInvalidCode(),
285
            $this->messageInvalidCode()
286
        );
287
    }
288
289
    /**
290
     * @return string
291
     */
292
    protected function messageInvalidCode(): string
293
    {
294
        return Craft::t('patron', 'Invalid code.');
295
    }
296
297
    /**
298
     * HTTP not found response code
299
     *
300
     * @return int
301
     */
302
    protected function statusCodeInvalidCode(): int
303
    {
304
        return 404;
305
    }
306
}
307