Passed
Push — master ( 759e05...3e6bbe )
by Hergen
12:04
created

jwtAuthRoles::getClaims()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
c 0
b 0
f 0
dl 0
loc 8
rs 10
cc 2
nc 2
nop 1
1
<?php
2
3
namespace werk365\jwtauthroles;
4
5
use werk365\jwtauthroles\Models\JwtUser;
6
use Firebase\JWT\JWT;
7
use Illuminate\Support\Facades\Http;
0 ignored issues
show
Bug introduced by
The type Illuminate\Support\Facades\Http was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
8
use Illuminate\Support\Str;
9
use phpseclib\Crypt\RSA;
10
use phpseclib\Math\BigInteger;
11
use Spatie\Permission\Models\Role;
0 ignored issues
show
Bug introduced by
The type Spatie\Permission\Models\Role was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
12
use werk365\jwtauthroles\Exceptions\authException;
13
use werk365\jwtauthroles\Models\JwtKey;
14
15
class jwtAuthRoles
16
{
17
18
    private static function getKid(string $jwt): ?string
19
    {
20
        if (Str::is('*.*.*', $jwt)) {
21
            $header = JWT::jsonDecode(JWT::urlsafeB64Decode(Str::before($jwt, '.')));
22
            if (isset($header->alg) && $header->alg !== config('jwtAuthRoles.alg')) {
23
                throw authException::auth(422, 'Invalid algorithm');
24
            }
25
26
            return $header->kid ?? null;
27
        } else {
28
            throw authException::auth(422, 'Malformed JWT');
29
        }
30
    }
31
32
    private static function getClaims(string $jwt): ?object
33
    {
34
        if (Str::is('*.*.*', $jwt)) {
35
            $claims = explode('.', $jwt);
36
            $claims = JWT::jsonDecode(JWT::urlsafeB64Decode($claims[1]));
37
            return $claims ?? null;
38
        } else {
39
            throw authException::auth(422, 'Malformed JWT');
40
        }
41
    }
42
43
    /**
44
     * @param object $jwk
45
     * @return bool|string|null
46
     */
47
    private static function jwkToPem(object $jwk)
48
    {
49
        if (isset($jwk->e) && isset($jwk->n)) {
50
            $rsa = new RSA();
51
            $rsa->loadKey([
52
                'e' => new BigInteger(JWT::urlsafeB64Decode($jwk->e), 256),
53
                'n' => new BigInteger(JWT::urlsafeB64Decode($jwk->n), 256),
54
            ]);
55
56
            return $rsa->getPublicKey();
57
        }
58
        throw authException::auth(500, 'Malformed jwk');
59
    }
60
61
    /**
62
     * @param string $kid
63
     * @param string $uri
64
     * @return bool|string|null
65
     */
66
    private static function getJwk(string $kid, string $uri)
67
    {
68
        $response = Http::get($uri);
69
        $json = $response->getBody();
70
        if ($json) {
71
            $jwks = json_decode($json, false);
72
            if ($jwks && isset($jwks->keys) && is_array($jwks->keys)) {
73
                foreach ($jwks->keys as $jwk) {
74
                    if ($jwk->kid === $kid) {
75
                        return self::jwkToPem($jwk);
76
                    }
77
                }
78
            }
79
        }
80
        throw authException::auth(404, 'jwks endpoint not found');
81
    }
82
83
    private static function getPem(string $kid, string $uri): ?string
84
    {
85
        $response = Http::get($uri);
86
        $json = $response->getBody();
87
        if ($json) {
88
            $pems = json_decode($json, false);
89
            if ($pems && isset($pems->publicKeys) && is_object($pems->publicKeys)) {
90
                foreach ($pems->publicKeys as $key=>$pem) {
91
                    if ($key === $kid) {
92
                        return $pem;
93
                    }
94
                }
95
            }
96
        }
97
        throw authException::auth(404, 'pem endpoint not found');
98
    }
99
100
    private static function verifyToken(string $jwt, string $uri, bool $jwk = false): object
101
    {
102
        $kid = self::getKid($jwt);
103
        if (! $kid) {
104
            throw authException::auth(422, 'Malformed JWT');
105
        }
106
        if (config('jwtAuthRoles.cache.enabled')) {
107
            if (config('jwtAuthRoles.cache.type') === 'database') {
108
                $row = JwtKey::where('kid', $kid)
109
                    ->orderBy('created_at', 'desc')
110
                    ->first('key');
111
            }
112
        }
113
114
        $publicKey = $row->key
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $row does not seem to be defined for all execution paths leading up to this point.
Loading history...
115
            ?? $jwk
116
                ? self::getJwk($kid, $uri)
117
                : self::getPem($kid, $uri);
118
119
        if (! isset($publicKey) || ! $publicKey) {
120
            throw authException::auth(500, 'Unable to validate JWT');
121
        }
122
123
        if (config('jwtAuthRoles.cache.enabled')) {
124
            if (config('jwtAuthRoles.cache.type') === 'database') {
125
                $row = $row ?? JwtKey::create(['kid' => $kid, 'key' => $publicKey]);
0 ignored issues
show
Unused Code introduced by
The assignment to $row is dead and can be removed.
Loading history...
126
            }
127
        }
128
129
        return JWT::decode($jwt, $publicKey, [config('jwtAuthRoles.alg')]);
130
    }
131
132
    /** @return mixed */
133
    public static function authUser(object $request)
134
    {
135
        $jwt = $request->bearerToken();
136
137
        $uri = config('jwtAuthRoles.useJwk')
138
            ? config('jwtAuthRoles.jwkUri')
139
            : config('jwtAuthRoles.pemUri');
140
141
        if (!config('jwtAuthRoles.validateJwt')) {
142
            $claims = self::getClaims($jwt);
143
        } else {
144
            $claims = self::verifyToken($jwt, $uri, config('jwtAuthRoles.useJwk'));
145
        }
146
147
        if (config('jwtAuthRoles.autoCreateUser')) {
148
            $user = JwtUser::firstOrNew([config('jwtAuthRoles.userId') =>  $claims->sub]);
149
            $user[config('jwtAuthRoles.userId')] = $claims->sub;
150
            $user->jwt = json_encode($claims);
151
            $user->save();
152
        } else {
153
            $user = JwtUser::where(config('jwtAuthRoles.userId'), '=', $claims->sub)->firstOrFail();
154
            $user->jwt = json_encode($claims);
155
            $user->save();
156
        }
157
158
        if (config('jwtAuthRoles.usePermissions')) {
159
            if (config('jwtAuthRoles.autoCreateRoles')) {
160
                foreach ($claims->roles as $role) {
161
                    $db_role = Role::where('name', $role)->first();
162
                    if (! $db_role) {
163
                        Role::create(['name' => $role, 'guard_name' => 'jwt']);
164
                    }
165
                }
166
            }
167
            // Remove previously assigned roles and update from JWT
168
            $user->syncRoles($claims->roles);
169
        }
170
171
        return $user;
172
    }
173
}
174