Callback::run()   A
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
dl 0
loc 24
ccs 0
cts 17
cp 0
rs 9.2248
c 0
b 0
f 0
cc 5
nc 4
nop 0
crap 30
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 League\OAuth2\Client\Token\AccessTokenInterface;
21
use yii\base\Event;
22
use yii\web\HttpException;
23
24
/**
25
 * @author Flipbox Factory <[email protected]>
26
 * @since 1.0.0
27
 */
28
class Callback extends Action
29
{
30
    use LookupTrait;
31
32
    /**
33
     * @inheritdoc
34
     * @throws \yii\base\InvalidConfigException
35
     */
36
    public function run()
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::getInstance()->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 AbstractProvider $provider
76
     * @return AccessToken|mixed
77
     * @throws HttpException
78
     */
79
    public function runInternal(
80
        $code,
81
        AbstractProvider $provider
82
    ) {
83
        if (($access = $this->checkAccess($provider)) !== true) {
84
            return $access;
85
        }
86
87
        return $this->performAction($code, $provider);
88
    }
89
90
    /**
91
     * @param $code
92
     * @param AbstractProvider $provider
93
     * @return AccessToken
94
     * @throws HttpException
95
     */
96
    public function performAction(
97
        $code,
98
        AbstractProvider $provider
99
    ): AccessToken {
100
101
        return $this->handleExceptions(function () use ($code, $provider) {
102
103
            // Get token via authorization code grant.
104
            $accessToken = $provider->getAccessToken(
105
                'authorization_code',
106
                [
107
                    'code' => $code
108
                ]
109
            );
110
111
            // Save token
112
            $this->persistNewToken(
113
                $accessToken,
114
                $provider
115
            );
116
117
            return $accessToken;
118
        });
119
    }
120
121
    /**
122
     * @param AccessTokenInterface $accessToken
123
     * @param AbstractProvider $provider
124
     * @return bool
125
     * @throws \Exception
126
     */
127
    protected function persistNewToken(
128
        AccessTokenInterface $accessToken,
129
        AbstractProvider $provider
130
    ): bool {
131
132
        $record = new Token();
133
134
        $record->setAttributes(
135
            [
136
                'accessToken' => $accessToken->getToken(),
137
                'refreshToken' => $accessToken->getRefreshToken(),
138
                'providerId' => ProviderHelper::lookupId($provider),
139
                'values' => $accessToken->getValues(),
140
                'dateExpires' => $accessToken->getExpires(),
141
                'enabled' => true
142
            ]
143
        );
144
145
        $event = new PersistToken([
146
            'token' => $accessToken,
147
            'provider' => $provider,
148
            'record' => $record
149
        ]);
150
151
        Event::trigger(
152
            get_class($provider),
153
            Patron::EVENT_BEFORE_PERSIST_TOKEN,
154
            $event
155
        );
156
157
        if (!$record->save()) {
158
            return false;
159
        }
160
161
        Event::trigger(
162
            get_class($provider),
163
            Patron::EVENT_AFTER_PERSIST_TOKEN,
164
            $event
165
        );
166
167
        return true;
168
    }
169
170
    /**
171
     * @param string|null $state
172
     * @return bool
173
     */
174
    protected function isValidState(string $state = null): bool
175
    {
176
        if ($state !== Patron::getInstance()->getSession()->getState()) {
177
            return false;
178
        }
179
        return true;
180
    }
181
182
    /**
183
     * @param string|null $url
184
     * @return string
185
     */
186
    protected function resolveRedirectUrl(string $url = null)
187
    {
188
        if (empty($url)) {
189
            $url = Patron::getInstance()->getSettings()->getCallbackUrl();
190
        }
191
192
        return $url;
193
    }
194
195
196
    /*******************************************
197
     * INVALID STATE RESPONSE
198
     *******************************************/
199
200
    /**
201
     * @param string|null $state
202
     * @throws HttpException
203
     */
204
    protected function handleInvalidStateResponse(string $state = null)
205
    {
206
        Patron::warning(Craft::t(
207
            'patron',
208
            "Invalid state: '{state}'",
209
            [
210
                '{state}' => $state
211
            ]
212
        ));
213
214
        throw new HttpException(
215
            $this->statusCodeInvalidState(),
216
            $this->messageInvalidState()
217
        );
218
    }
219
220
    /**
221
     * @return string
222
     */
223
    protected function messageInvalidState(): string
224
    {
225
        return Craft::t('patron', 'Invalid state.');
226
    }
227
228
    /**
229
     * HTTP not found response code
230
     *
231
     * @return int
232
     */
233
    protected function statusCodeInvalidState(): int
234
    {
235
        return 404;
236
    }
237
238
239
    /*******************************************
240
     * USER NOT FOUND RESPONSE
241
     *******************************************/
242
243
    /**
244
     * @throws HttpException
245
     */
246
    protected function handleUserNotFoundResponse()
247
    {
248
        throw new HttpException(
249
            $this->statusCodeUserNotFound(),
250
            $this->messageUserNotFound()
251
        );
252
    }
253
254
    /**
255
     * @return string
256
     */
257
    protected function messageUserNotFound(): string
258
    {
259
        return Craft::t('patron', 'Unable to find user.');
260
    }
261
262
    /**
263
     * HTTP not found response code
264
     *
265
     * @return int
266
     */
267
    protected function statusCodeUserNotFound(): int
268
    {
269
        return 404;
270
    }
271
272
273
    /*******************************************
274
     * INVALID CODE RESPONSE
275
     *******************************************/
276
277
    /**
278
     * @throws HttpException
279
     */
280
    protected function handleInvalidCodeResponse()
281
    {
282
        throw new HttpException(
283
            $this->statusCodeInvalidCode(),
284
            $this->messageInvalidCode()
285
        );
286
    }
287
288
    /**
289
     * @return string
290
     */
291
    protected function messageInvalidCode(): string
292
    {
293
        return Craft::t('patron', 'Invalid code.');
294
    }
295
296
    /**
297
     * HTTP not found response code
298
     *
299
     * @return int
300
     */
301
    protected function statusCodeInvalidCode(): int
302
    {
303
        return 404;
304
    }
305
}
306