Completed
Push — master ( 951200...118666 )
by Nate
05:58 queued 03:32
created

SelfConsumable::verifyTokenCsrfClaim()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 0
cts 12
cp 0
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 8
nc 2
nop 1
crap 6
1
<?php
2
3
/**
4
 * @copyright  Copyright (c) Flipbox Digital Limited
5
 * @license    https://flipboxfactory.com/software/jwt/license
6
 * @link       https://www.flipboxfactory.com/jwt/organization/
7
 */
8
9
namespace flipbox\craft\jwt\services;
10
11
use Craft;
12
use craft\elements\User;
13
use flipbox\craft\jwt\Jwt;
14
use Lcobucci\JWT\Token;
15
use yii\base\Component;
16
use yii\web\IdentityInterface;
17
18
/**
19
 * @author Flipbox Factory <[email protected]>
20
 * @since 1.0.0
21
 */
22
class SelfConsumable extends Component
23
{
24
    /**
25
     * The CSRF claim identifier
26
     */
27
    const CLAIM_CSRF = 'csrf';
28
29
    /**
30
     * The Audience claim identifier
31
     */
32
    const CLAIM_AUDIENCE = 'aud';
33
34
    /**
35
     * The Issuer claim identifier
36
     */
37
    const CLAIM_ISSUER = 'iss';
38
39
    /**
40
     * The Identity claim identifier
41
     */
42
    const CLAIM_IDENTITY = 'jti';
43
44
    /**
45
     * Issue an authorization JWT token on behalf of a user.
46
     *
47
     * @param string $user
48
     * @param string|null $audience
49
     * @param int|null $expiration
50
     * @return Token|null
51
     * @throws \craft\errors\SiteNotFoundException
52
     * @throws \yii\base\InvalidConfigException
53
     */
54
    public function issue(
55
        $user = 'CURRENT_USER',
56
        string $audience = null,
57
        int $expiration = null
58
    ) {
59
        if (null === ($identity = $this->resolveUser($user))) {
60
            return null;
61
        }
62
63
        return Jwt::getInstance()->getBuilder()
64
            ->setIssuer(Jwt::getInstance()->getSettings()->getIssuer())
65
            ->setAudience($this->resolveAudience($audience))
66
            ->setId($identity->getId(), true)
67
            ->setIssuedAt(time())
68
            ->setNotBefore(time())
69
            ->setExpiration($this->resolveTokenExpiration($expiration))
70
            ->set(self::CLAIM_CSRF, Craft::$app->getRequest()->getCsrfToken())
71
            ->sign(Jwt::getInstance()->getSettings()->getSigner(), $this->getSignatureKey($identity))
72
            ->getToken();
73
    }
74
75
    /**
76
     * This
77
     * @param string $token
78
     * @return null|IdentityInterface
79
     * @throws \craft\errors\SiteNotFoundException
80
     */
81
    public function claim(string $token)
82
    {
83
        if (null === ($token = $this->parse($token))) {
84
            return null;
85
        }
86
87
        return $this->tokenIdentity($token);
88
    }
89
90
    /**
91
     * @param $token
92
     * @param bool $validate
93
     * @param bool $verify
94
     * @return Token|null
95
     * @throws \craft\errors\SiteNotFoundException
96
     */
97
    public function parse(string $token, bool $validate = true, bool $verify = true)
98
    {
99
        try {
100
            $token = Jwt::getInstance()->getParser()->parse((string)$token);
101
        } catch (\RuntimeException $e) {
102
            Jwt::warning("Invalid JWT provided: " . $e->getMessage());
103
            return null;
104
        } catch (\InvalidArgumentException $e) {
105
            Jwt::warning("Invalid JWT provided: " . $e->getMessage());
106
            return null;
107
        }
108
109
        if (($validate && !Jwt::getInstance()->validateToken($token)) ||
110
            ($verify && !$this->verifyToken($token))) {
111
            return null;
112
        }
113
114
        return $token;
115
    }
116
117
    /**
118
     * @param Token $token
119
     * @return bool
120
     * @throws \craft\errors\SiteNotFoundException
121
     */
122
    public function verifyToken(Token $token): bool
123
    {
124
        if (null === ($identity = $this->resolveUser($token->getClaim(self::CLAIM_IDENTITY)))) {
125
            return false;
126
        }
127
128
        return $this->verifyTokenCsrfClaim($token) &&
129
            $this->verifyIssuer($token) &&
130
            $this->verifyAudience($token) &&
131
            $this->verifyTokenSignature($token, $identity);
132
    }
133
134
    /**
135
     * @param Token $token
136
     * @return null|IdentityInterface
137
     */
138
    private function tokenIdentity(Token $token)
139
    {
140
        return $this->resolveUser($token->getClaim(self::CLAIM_IDENTITY));
141
    }
142
143
    /**
144
     * @param Token $token
145
     * @return bool
146
     * @throws \craft\errors\SiteNotFoundException
147
     */
148
    private function verifyAudience(Token $token): bool
149
    {
150
        $audience = $token->getClaim(self::CLAIM_AUDIENCE);
151
        if (false === ($audience === Craft::$app->getSites()->getCurrentSite()->baseUrl)) {
152
            Jwt::error(sprintf(
153
                "Unable to verify audience: %s",
154
                $audience
155
            ));
156
157
            return false;
158
        }
159
        return true;
160
    }
161
162
    /**
163
     * Verify that the issuer is one we can accept from
164
     *
165
     * @param Token $token
166
     * @return bool
167
     * @throws \craft\errors\SiteNotFoundException
168
     */
169
    private function verifyIssuer(Token $token): bool
170
    {
171
        $issuer = $token->getClaim(self::CLAIM_ISSUER);
172
        if (false === in_array(
173
            $issuer,
174
            Jwt::getInstance()->getSettings()->getSelfConsumableIssuers()
175
        )) {
176
            Jwt::error(sprintf(
177
                "Unable to verify issuer: %s",
178
                $issuer
179
            ));
180
181
            return false;
182
        }
183
        return true;
184
    }
185
186
    /**
187
     * @param Token $token
188
     * @return bool
189
     */
190
    private function verifyTokenCsrfClaim(Token $token): bool
191
    {
192
        $csrf = $token->getClaim(self::CLAIM_CSRF);
193
        if (false === Craft::$app->getRequest()->validateCsrfToken($csrf)) {
194
            Jwt::error(sprintf(
195
                "Unable to verify CSRF Token: %s",
196
                $csrf
197
            ));
198
199
            return false;
200
        }
201
        return true;
202
    }
203
204
    /**
205
     * @param Token $token
206
     * @param IdentityInterface $identity
207
     * @return bool
208
     */
209
    private function verifyTokenSignature(Token $token, IdentityInterface $identity): bool
210
    {
211
        try {
212
            if (false === $token->verify(
213
                Jwt::getInstance()->getSettings()->resolveSigner($token->getHeader('alg')),
214
                $this->getSignatureKey($identity)
215
            )) {
216
                Jwt::error("Unable to verify token signature");
217
                return false;
218
            }
219
            return true;
220
        } catch (\Exception $e) {
221
            Jwt::error(sprintf(
222
                "Exception caught while trying to verify token signature: %s",
223
                $e->getMessage()
224
            ));
225
        }
226
        return false;
227
    }
228
229
    /**
230
     * @param IdentityInterface $identity
231
     * @return string
232
     */
233
    private function getSignatureKey(IdentityInterface $identity)
234
    {
235
        $id = $identity instanceof User ? $identity->uid : $identity->getId();
236
        return Jwt::getInstance()->getSettings()->getKey() . '.' . $id;
237
    }
238
239
    /**
240
     * @param string|null $audience
241
     * @return string
242
     * @throws \craft\errors\SiteNotFoundException
243
     */
244
    private function resolveAudience(string $audience = null): string
245
    {
246
        if ($audience === null) {
247
            $audience = Jwt::getInstance()->getSettings()->getSelfConsumableAudience();
248
        }
249
250
        return (string)$audience;
251
    }
252
253
    /**
254
     * @param int|null $expiration
255
     * @return int
256
     */
257
    private function resolveTokenExpiration(int $expiration = null): int
258
    {
259
        if ($expiration === null) {
260
            $expiration = Jwt::getInstance()->getSettings()->tokenExpiration;
261
        }
262
263
        return time() + (int)$expiration;
264
    }
265
266
    /**
267
     * @param $user
268
     * @return IdentityInterface|null
269
     */
270
    private function resolveUser($user)
271
    {
272
        if ($user instanceof IdentityInterface) {
273
            return $user;
274
        }
275
276
        if ($user === 'CURRENT_USER') {
277
            return Craft::$app->getUser()->getIdentity();
278
        }
279
280
        if (is_numeric($user)) {
281
            return Craft::$app->getUsers()->getUserById($user);
282
        }
283
284
        if (is_string($user)) {
285
            return Craft::$app->getUsers()->getUserByUsernameOrEmail($user);
286
        }
287
288
        return null;
289
    }
290
}
291