Passed
Push — bugfix/relatives_not_saving ( 6485fa...f1e1c5 )
by Tristan
13:39
created

JwtKeysFetcher::getByKID()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 8
dl 0
loc 13
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
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
 */
15
class JwtKeysFetcher
16
{
17
    /**
18
     * @var JSONGetter
19
     */
20
    private $fetcher;
21
    /**
22
     * @var string
23
     */
24
    private $jwksURI;
25
    /**
26
     * @var Decoder
27
     */
28
    private $decoder;
29
    /**
30
     * @var CacheRepository
31
     */
32
    private $cache;
33
    /**
34
     * @var int
0 ignored issues
show
Bug introduced by
Expected "integer" but found "int" for @var tag in member variable comment
Loading history...
35
     */
36
    private $cacheHourLimit;
37
    /**
38
     * JwtKeysFetcher constructor.
39
     *
40
     * @param JSONGetter     $fetcher
2 ignored issues
show
Coding Style Documentation 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
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
42
     * @param Decoder         $decoder
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
43
     * @param string          $jwksURI
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
44
     * @param int             $cacheHourLimit
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "integer" but found "int" for parameter type
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
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
58
     *
59
     * @return Key|null
60
     */
61
    public function getByKID(string $kid)
0 ignored issues
show
introduced by
Method \App\Services\Auth\JwtKeysFetcher::getByKID() does not have return type hint for its return value but it should be possible to add it based on @return annotation "Key|null".
Loading history...
62
    {
63
        $cacheKey = 'keys.' . $kid;
64
        if ($this->cache->has($cacheKey)) {
65
            return $this->cache->get($cacheKey);
66
        }
67
        /** @var Key[] $keys */
68
        $keys = $this->fetch();
69
        if (!isset($keys[$kid])) {
70
            return null;
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
0 ignored issues
show
introduced by
@return annotation of method \App\Services\Auth\JwtKeysFetcher::fetch() does not specify type hint for items of its traversable return value.
Loading history...
79
     */
80
    public function fetch()
0 ignored issues
show
introduced by
Method \App\Services\Auth\JwtKeysFetcher::fetch() does not have return type hint for its return value but it should be possible to add it based on @return annotation "array".
Loading history...
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
92
     *
93
     * @param string $n the RSA modulus encoded in URL Safe Base64
2 ignored issues
show
Coding Style introduced by
Parameter comment must start with a capital letter
Loading history...
Coding Style Documentation introduced by
Parameter comment must end with a full stop
Loading history...
94
     * @param string $e the RSA exponent encoded in URL Safe Base64
2 ignored issues
show
Coding Style introduced by
Parameter comment must start with a capital letter
Loading history...
Coding Style Documentation introduced by
Parameter comment must end with a full stop
Loading history...
95
     *
96
     * @return string the RSA public key represented in PEM format
97
     */
98
    protected function createPemFromModulusAndExponent(string $n, string $e)
0 ignored issues
show
introduced by
Method \App\Services\Auth\JwtKeysFetcher::createPemFromModulusAndExponent() does not have return type hint for its return value but it should be possible to add it based on @return annotation "string".
Loading history...
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
1 ignored issue
show
Coding Style Documentation introduced by
Missing parameter comment
Loading history...
Coding Style introduced by
Expected "integer" but found "int" for parameter type
Loading history...
135
     *
136
     * @return string
137
     */
138
    protected function encodeLength(int $length)
0 ignored issues
show
introduced by
Method \App\Services\Auth\JwtKeysFetcher::encodeLength() does not have return type hint for its return value but it should be possible to add it based on @return annotation "string".
Loading history...
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
}
147