Completed
Push — master ( 19b2ae...0e0de4 )
by Nate
04:43
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 35
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 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
        $patron = Patron::getInstance();
39
40
        // Get code
41
        if (!$code = Craft::$app->getRequest()->getParam('code')) {
42
            return $this->handleInvalidCodeResponse();
43
        }
44
45
        // Validate state
46
        $state = Craft::$app->getRequest()->getParam('state');
47
        if (!$this->isValidState($state)) {
48
            return $this->handleInvalidStateResponse($state);
49
        }
50
51
        // Get provider
52
        $identifier = $patron->getSession()->getProvider();
53
        if (!$identifier || (!$provider = $this->find($identifier))) {
54
            return $this->handleNotFoundResponse();
55
        }
56
57
        return $this->runInternal(
58
            $code,
59
            $provider
60
        );
61
    }
62
63
    /**
64
     * @param int $id
65
     * @return AbstractProvider
66
     * @throws \yii\base\InvalidConfigException
67
     */
68
    protected function find(int $id)
69
    {
70
        return (new ProviderQuery())
71
            ->id($id)
72
            ->one();
73
    }
74
75
    /**
76
     * @param $code
77
     * @param AbstractProvider $provider
78
     * @return AccessToken|mixed
79
     * @throws HttpException
80
     */
81
    public function runInternal(
82
        $code,
83
        AbstractProvider $provider
84
    ) {
85
        if (($access = $this->checkAccess($provider)) !== true) {
86
            return $access;
87
        }
88
89
        return $this->performAction($code, $provider);
90
    }
91
92
    /**
93
     * @param $code
94
     * @param AbstractProvider $provider
95
     * @return AccessToken
96
     * @throws HttpException
97
     */
98
    public function performAction(
99
        $code,
100
        AbstractProvider $provider
101
    ): AccessToken {
102
103
        return $this->handleExceptions(function () use ($code, $provider) {
104
105
            // Get token via authorization code grant.
106
            $accessToken = $provider->getAccessToken(
107
                'authorization_code',
108
                [
109
                    'code' => $code
110
                ]
111
            );
112
113
            // Save token
114
            $this->persistNewToken(
115
                $accessToken,
116
                $provider
117
            );
118
119
            return $accessToken;
120
        });
121
    }
122
123
    /**
124
     * @param AccessTokenInterface $accessToken
125
     * @param AbstractProvider $provider
126
     * @return bool
127
     * @throws \Exception
128
     */
129
    protected function persistNewToken(
130
        AccessTokenInterface $accessToken,
131
        AbstractProvider $provider
132
    ): bool {
133
134
        $record = new Token();
135
136
        $record->setAttributes(
137
            [
138
                'accessToken' => $accessToken->getToken(),
139
                'refreshToken' => $accessToken->getRefreshToken(),
140
                'providerId' => ProviderHelper::lookupId($provider),
141
                'values' => $accessToken->getValues(),
142
                'dateExpires' => $accessToken->getExpires(),
143
                'enabled' => true
144
            ]
145
        );
146
147
        $event = new PersistToken([
148
            'token' => $accessToken,
149
            'provider' => $provider,
150
            'record' => $record
151
        ]);
152
153
        Event::trigger(
154
            get_class($provider),
155
            Patron::EVENT_BEFORE_PERSIST_TOKEN,
156
            $event
157
        );
158
159
        if (!$record->save()) {
160
            return false;
161
        }
162
163
        Event::trigger(
164
            get_class($provider),
165
            Patron::EVENT_AFTER_PERSIST_TOKEN,
166
            $event
167
        );
168
169
        return true;
170
    }
171
172
    /**
173
     * @param string|null $state
174
     * @return bool
175
     */
176
    protected function isValidState(string $state = null): bool
177
    {
178
        if ($state !== Patron::getInstance()->getSession()->getState()) {
179
            return false;
180
        }
181
        return true;
182
    }
183
184
    /**
185
     * @param string|null $url
186
     * @return string
187
     */
188
    protected function resolveRedirectUrl(string $url = null)
189
    {
190
        if (empty($url)) {
191
            $url = Patron::getInstance()->getSettings()->getCallbackUrl();
192
        }
193
194
        return $url;
195
    }
196
197
198
    /*******************************************
199
     * INVALID STATE RESPONSE
200
     *******************************************/
201
202
    /**
203
     * @param string|null $state
204
     * @throws HttpException
205
     */
206
    protected function handleInvalidStateResponse(string $state = null)
207
    {
208
        Patron::warning(Craft::t(
209
            'patron',
210
            "Invalid state: '{state}'",
211
            [
212
                '{state}' => $state
213
            ]
214
        ));
215
216
        throw new HttpException(
217
            $this->statusCodeInvalidState(),
218
            $this->messageInvalidState()
219
        );
220
    }
221
222
    /**
223
     * @return string
224
     */
225
    protected function messageInvalidState(): string
226
    {
227
        return Craft::t('patron', 'Invalid state.');
228
    }
229
230
    /**
231
     * HTTP not found response code
232
     *
233
     * @return int
234
     */
235
    protected function statusCodeInvalidState(): int
236
    {
237
        return 404;
238
    }
239
240
241
    /*******************************************
242
     * USER NOT FOUND RESPONSE
243
     *******************************************/
244
245
    /**
246
     * @throws HttpException
247
     */
248
    protected function handleUserNotFoundResponse()
249
    {
250
        throw new HttpException(
251
            $this->statusCodeUserNotFound(),
252
            $this->messageUserNotFound()
253
        );
254
    }
255
256
    /**
257
     * @return string
258
     */
259
    protected function messageUserNotFound(): string
260
    {
261
        return Craft::t('patron', 'Unable to find user.');
262
    }
263
264
    /**
265
     * HTTP not found response code
266
     *
267
     * @return int
268
     */
269
    protected function statusCodeUserNotFound(): int
270
    {
271
        return 404;
272
    }
273
274
275
    /*******************************************
276
     * INVALID CODE RESPONSE
277
     *******************************************/
278
279
    /**
280
     * @throws HttpException
281
     */
282
    protected function handleInvalidCodeResponse()
283
    {
284
        throw new HttpException(
285
            $this->statusCodeInvalidCode(),
286
            $this->messageInvalidCode()
287
        );
288
    }
289
290
    /**
291
     * @return string
292
     */
293
    protected function messageInvalidCode(): string
294
    {
295
        return Craft::t('patron', 'Invalid code.');
296
    }
297
298
    /**
299
     * HTTP not found response code
300
     *
301
     * @return int
302
     */
303
    protected function statusCodeInvalidCode(): int
304
    {
305
        return 404;
306
    }
307
}
308