Completed
Push — dev ( 8f8cd7...5b0430 )
by Tristan
16s
created

JwtKeysFetcher::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 7
ccs 0
cts 6
cp 0
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 5
crap 2
1
<?php
2
0 ignored issues
show
Coding Style introduced by
Missing file doc comment
Loading history...
3
namespace App\Services\Auth;
4
5
use Carbon\Carbon;
6
use App\Services\Auth\Contracts\JSONGetter;
7
use Illuminate\Contracts\Cache\Repository as CacheRepository;
8
use Lcobucci\JWT\Parsing\Decoder;
9
use Lcobucci\JWT\Signer\Key;
10
11
/**
12
 * Adapted from the OpenIDConnect Laravel package at
13
 * https://github.com/furdarius/oidconnect-laravel
14
 */
0 ignored issues
show
Coding Style introduced by
Missing @license tag in class comment
Loading history...
Coding Style introduced by
Missing @category tag in class comment
Loading history...
Coding Style introduced by
Missing @author tag in class comment
Loading history...
Coding Style introduced by
Missing @link tag in class comment
Loading history...
Coding Style introduced by
Missing @package tag in class comment
Loading history...
15
class JwtKeysFetcher
16
{
17
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
18
     * @var JSONGetter
19
     */
20
    private $fetcher;
0 ignored issues
show
Coding Style introduced by
Private member variable "fetcher" must be prefixed with an underscore
Loading history...
21
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
22
     * @var string
23
     */
24
    private $jwksURI;
0 ignored issues
show
Coding Style introduced by
Private member variable "jwksURI" must be prefixed with an underscore
Loading history...
25
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
26
     * @var Decoder
27
     */
28
    private $decoder;
0 ignored issues
show
Coding Style introduced by
Private member variable "decoder" must be prefixed with an underscore
Loading history...
29
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
30
     * @var CacheRepository
31
     */
32
    private $cache;
0 ignored issues
show
Coding Style introduced by
Private member variable "cache" must be prefixed with an underscore
Loading history...
33
    /**
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
34
     * @var int
35
     */
36
    private $cacheHourLimit;
0 ignored issues
show
Coding Style introduced by
Private member variable "cacheHourLimit" must be prefixed with an underscore
Loading history...
37
    /**
38
     * JwtKeysFetcher constructor.
39
     *
40
     * @param JSONGetter     $fetcher
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected 6 spaces after parameter type; 5 found
Loading history...
41
     * @param CacheRepository $cache
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
42
     * @param Decoder         $decoder
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
43
     * @param string          $jwksURI
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
44
     * @param int             $cacheHourLimit
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
45
     */
46
    public function __construct(JSONGetter $fetcher, CacheRepository $cache, Decoder $decoder, string $jwksURI, int $cacheHourLimit)
47
    {
48
        $this->fetcher = $fetcher;
49
        $this->cache = $cache;
50
        $this->jwksURI = $jwksURI;
51
        $this->decoder = $decoder;
52
        $this->cacheHourLimit = $cacheHourLimit;
53
    }
54
    /**
55
     * Fetch JWK key from JWKs URI with defined kid
56
     *
57
     * @param string $kid
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
58
     *
59
     * @return Key|null
60
     */
61
    public function getByKID(string $kid): Key
62
    {
63
        $cacheKey = 'keys.' . $kid;
64
        if ($this->cache->has($cacheKey)) {
65
            return $this->cache->get($cacheKey);
66
        }
67
        /** @var Key[] $keys */
0 ignored issues
show
Coding Style introduced by
Missing short description in doc comment
Loading history...
Coding Style introduced by
The close comment tag must be the only content on the line
Loading history...
Coding Style introduced by
The open comment tag must be the only content on the line
Loading history...
68
        $keys = $this->fetch();
69
        if (!isset($keys[$kid])) {
70
            return null;
0 ignored issues
show
Bug Best Practice introduced by
The expression return null returns the type null which is incompatible with the type-hinted return Lcobucci\JWT\Signer\Key.
Loading history...
71
        }
72
        $this->cache->put($cacheKey, $keys[$kid], Carbon::now()->addHours($this->cacheHourLimit));
73
        return $keys[$kid];
74
    }
75
    /**
76
     * Fetch list of JWKs from JWKs URI
77
     *
78
     * @return array
79
     */
80
    public function fetch(): array
81
    {
82
        $result = [];
83
        $data = $this->fetcher->get($this->jwksURI);
84
        foreach ($data['keys'] as $key) {
85
            $result[$key['kid']] = new Key($this->createPemFromModulusAndExponent($key['n'], $key['e']));
86
        }
87
        return $result;
88
    }
89
    /**
90
     *
91
     * Create a public key represented in PEM format from RSA modulus and exponent information
0 ignored issues
show
Coding Style introduced by
Doc comment short description must be on the first line
Loading history...
92
     *
93
     * @param string $n the RSA modulus encoded in URL Safe Base64
94
     * @param string $e the RSA exponent encoded in URL Safe Base64
95
     *
96
     * @return string the RSA public key represented in PEM format
97
     */
98
    protected function createPemFromModulusAndExponent(string $n, string $e): string
99
    {
100
        $modulus = $this->decoder->base64UrlDecode($n);
101
        $publicExponent = $this->decoder->base64UrlDecode($e);
102
        $components = [
103
            'modulus' => pack('Ca*a*', 2, $this->encodeLength(strlen($modulus)), $modulus),
104
            'publicExponent' => pack('Ca*a*', 2, $this->encodeLength(strlen($publicExponent)), $publicExponent),
105
        ];
106
        $RSAPublicKey = pack(
107
            'Ca*a*a*',
108
            48,
109
            $this->encodeLength(strlen($components['modulus']) + strlen($components['publicExponent'])),
110
            $components['modulus'],
111
            $components['publicExponent']
112
        );
113
        $rsaOID = pack('H*', '300d06092a864886f70d0101010500');
114
        $RSAPublicKey = chr(0) . $RSAPublicKey;
115
        $RSAPublicKey = chr(3) . $this->encodeLength(strlen($RSAPublicKey)) . $RSAPublicKey;
116
        $RSAPublicKey = pack(
117
            'Ca*a*',
118
            48,
119
            $this->encodeLength(strlen($rsaOID . $RSAPublicKey)),
120
            $rsaOID . $RSAPublicKey
121
        );
122
        $RSAPublicKey = "-----BEGIN PUBLIC KEY-----" . PHP_EOL
123
            . chunk_split(base64_encode($RSAPublicKey), 64, PHP_EOL)
124
            . '-----END PUBLIC KEY-----';
125
        return $RSAPublicKey;
126
    }
127
    /**
128
     * DER-encode the length
129
     *
130
     * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See
131
     * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3}
132
     * for more information.
133
     *
134
     * @param int $length
0 ignored issues
show
Coding Style introduced by
Missing parameter comment
Loading history...
135
     *
136
     * @return string
137
     */
138
    protected function encodeLength(int $length): string
139
    {
140
        if ($length <= 0x7F) {
141
            return chr($length);
142
        }
143
        $temp = ltrim(pack('N', $length), chr(0));
144
        return pack('Ca*', 0x80 | strlen($temp), $temp);
145
    }
146
}