Completed
Pull Request — master (#7)
by John
02:17
created

JwtKey   B

Complexity

Total Complexity 38

Size/Duplication

Total Lines 189
Duplicated Lines 7.94 %

Coupling/Cohesion

Components 1
Dependencies 4

Importance

Changes 2
Bugs 0 Features 0
Metric Value
wmc 38
c 2
b 0
f 0
lcom 1
cbo 4
dl 15
loc 189
rs 8.3999

7 Methods

Rating   Name   Duplication   Size   Complexity  
C __construct() 0 29 7
A getId() 0 4 1
A validateToken() 0 11 2
A validateHeader() 0 12 4
D validateClaims() 15 36 21
A getSignatureValidator() 0 8 2
A __sleep() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
/*
3
 * This file is part of the KleijnWeb\JwtBundle package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
9
namespace KleijnWeb\JwtBundle\Authenticator;
10
11
use KleijnWeb\JwtBundle\Authenticator\SignatureValidator\SignatureValidator;
12
use KleijnWeb\JwtBundle\Authenticator\SignatureValidator\HmacValidator;
13
use KleijnWeb\JwtBundle\Authenticator\SignatureValidator\RsaValidator;
14
15
/**
16
 * @author John Kleijn <[email protected]>
17
 */
18
class JwtKey
19
{
20
    const TYPE_HMAC = 'HS256';
21
    const TYPE_RSA = 'RS256';
22
23
    /**
24
     * @var string
25
     */
26
    private $id;
27
28
    /**
29
     * @var string
30
     */
31
    private $issuer;
32
33
    /**
34
     * @var string
35
     */
36
    private $type = self::TYPE_HMAC;
37
38
    /**
39
     * @var string
40
     */
41
    private $audience;
42
43
    /**
44
     * @var int
45
     */
46
    private $minIssueTime;
47
48
    /**
49
     * @var array
50
     */
51
    private $requiredClaims = [];
52
53
    /**
54
     * @var int
55
     */
56
    private $issuerTimeLeeway;
57
58
    /**
59
     * @var string
60
     */
61
    private $secret;
62
63
    /**
64
     * @var SecretLoader
65
     */
66
    private $secretLoader;
67
68
    /**
69
     * @param array $options
70
     */
71
    public function __construct(array $options)
72
    {
73
        if (!isset($options['secret']) && !isset($options['loader'])) {
74
            throw new \InvalidArgumentException("Need a secret or a loader to verify tokens");
75
        }
76
        if (isset($options['secret']) && isset($options['loader'])) {
77
            throw new \InvalidArgumentException("Cannot configure both secret and loader");
78
        }
79
        $defaults = [
80
            'kid'          => null,
81
            'issuer'       => null,
82
            'audience'     => null,
83
            'minIssueTime' => null,
84
            'leeway'       => 0,
85
            'type'         => $this->type,
86
            'require'      => $this->requiredClaims,
87
        ];
88
89
        $options                = array_merge($defaults, $options);
90
        $this->issuer           = $options['issuer'];
91
        $this->audience         = $options['audience'];
92
        $this->type             = $options['type'];
93
        $this->minIssueTime     = $options['minIssueTime'];
94
        $this->requiredClaims   = $options['require'];
95
        $this->issuerTimeLeeway = $options['leeway'];
96
        $this->id               = $options['kid'];
97
        $this->secret           = isset($options['secret']) ? $options['secret'] : null;
98
        $this->secretLoader     = isset($options['loader']) ? $options['loader'] : null;
99
    }
100
101
    /**
102
     * @return string
103
     */
104
    public function getId()
105
    {
106
        return $this->id;
107
    }
108
109
    /**
110
     * @param JwtToken $token
111
     *
112
     * @throws \InvalidArgumentException
113
     */
114
    public function validateToken(JwtToken $token)
115
    {
116
        $this->validateHeader($token->getHeader());
117
        $this->validateClaims($token->getClaims());
118
119
        if (!$this->secretLoader) {
120
            $token->validateSignature($this->secret, $this->getSignatureValidator());
121
            return;
122
        }
123
        $token->validateSignature($this->secretLoader->load($token), $this->getSignatureValidator());
124
    }
125
126
    /**
127
     * @param array $header
128
     *
129
     * @throws \InvalidArgumentException
130
     */
131
    public function validateHeader(array $header)
132
    {
133
        if (!isset($header['alg'])) {
134
            throw new \InvalidArgumentException("Missing 'alg' in header");
135
        }
136
        if (!isset($header['typ'])) {
137
            throw new \InvalidArgumentException("Missing 'typ' in header");
138
        }
139
        if ($this->type !== $header['alg']) {
140
            throw new \InvalidArgumentException("Algorithm mismatch");
141
        }
142
    }
143
144
    /**
145
     * @param array $claims
146
     *
147
     * @throws \InvalidArgumentException
148
     */
149
    public function validateClaims(array $claims)
150
    {
151
        if ($this->requiredClaims) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->requiredClaims of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
152
            $missing = array_diff_key(array_flip($this->requiredClaims), $claims);
153
            if (count($missing)) {
154
                throw new \InvalidArgumentException("Missing claims: " . implode(', ', $missing));
155
            }
156
        }
157
        if ($this->issuer && !isset($claims['iss'])) {
158
            throw new \InvalidArgumentException("Claim 'iss' is required");
159
        }
160
        if ($this->minIssueTime && !isset($claims['iat'])) {
161
            throw new \InvalidArgumentException("Claim 'iat' is required");
162
        }
163
        if ($this->audience && !isset($claims['aud'])) {
164
            throw new \InvalidArgumentException("Claim 'aud' is required");
165
        }
166
        if (!isset($claims['prn']) || empty($claims['prn'])) {
167
            throw new \InvalidArgumentException("Missing principle claim");
168
        }
169 View Code Duplication
        if (isset($claims['exp']) && $claims['exp'] < time()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
170
            throw new \InvalidArgumentException("Token is expired by 'exp'");
171
        }
172 View Code Duplication
        if (isset($claims['iat']) && $claims['iat'] < $this->minIssueTime) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
173
            throw new \InvalidArgumentException("Server deemed your token too old");
174
        }
175 View Code Duplication
        if (isset($claims['nbf']) && $claims['nbf'] > time()) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
176
            throw new \InvalidArgumentException("Token not valid yet");
177
        }
178 View Code Duplication
        if (isset($claims['iss']) && $claims['iss'] !== $this->issuer) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
179
            throw new \InvalidArgumentException("Issuer mismatch");
180
        }
181 View Code Duplication
        if (isset($claims['aud']) && $claims['aud'] !== $this->audience) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
182
            throw new \InvalidArgumentException("Audience mismatch");
183
        }
184
    }
185
186
187
    /**
188
     * @return SignatureValidator
189
     */
190
    public function getSignatureValidator()
191
    {
192
        if ($this->type == self::TYPE_RSA) {
193
            return new RsaValidator();
194
        }
195
196
        return new HmacValidator();
197
    }
198
199
    /**
200
     * Prevent accidental persistence of secret
201
     */
202
    final public function __sleep()
203
    {
204
        return [];
205
    }
206
}
207