These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Firesphere\GraphQLJWT; |
||
4 | |||
5 | use BadMethodCallException; |
||
6 | use JWTException; |
||
7 | use Lcobucci\JWT\Builder; |
||
8 | use Lcobucci\JWT\Parser; |
||
9 | use Lcobucci\JWT\Signer\Hmac\Sha256; |
||
10 | use Lcobucci\JWT\Signer\Rsa\Sha256 as RsaSha256; |
||
11 | use Lcobucci\JWT\Signer\Key; |
||
12 | use Lcobucci\JWT\Token; |
||
13 | use Lcobucci\JWT\ValidationData; |
||
14 | use SilverStripe\Control\Director; |
||
15 | use SilverStripe\Control\HTTPRequest; |
||
16 | use SilverStripe\Core\Config\Configurable; |
||
17 | use SilverStripe\GraphQL\Controller; |
||
18 | use SilverStripe\ORM\ValidationException; |
||
19 | use SilverStripe\ORM\ValidationResult; |
||
20 | use SilverStripe\Security\Authenticator; |
||
21 | use SilverStripe\Security\Member; |
||
22 | use SilverStripe\Security\MemberAuthenticator\MemberAuthenticator; |
||
23 | |||
24 | class JWTAuthenticator extends MemberAuthenticator |
||
25 | { |
||
26 | use Configurable; |
||
27 | |||
28 | /** |
||
29 | * @var Sha256|RsaSha256 |
||
30 | */ |
||
31 | private $signer; |
||
32 | |||
33 | /** |
||
34 | * @var string|Key; |
||
35 | */ |
||
36 | private $privateKey; |
||
37 | |||
38 | /** |
||
39 | * @var string|Key; |
||
40 | */ |
||
41 | private $publicKey; |
||
42 | |||
43 | public function __construct() |
||
44 | { |
||
45 | $key = getenv('JWT_SIGNER_KEY'); |
||
46 | if (empty($key)) { |
||
47 | throw new JWTException('No key defined!', 1); |
||
48 | } |
||
49 | $publicKeyLocation = getenv('JWT_PUBLIC_KEY'); |
||
50 | if (file_exists($key) && !file_exists($publicKeyLocation)) { |
||
51 | throw new JWTException('No public key found!', 1); |
||
52 | } |
||
53 | } |
||
54 | |||
55 | /** |
||
56 | * Setup the keys this has to be done on the spot |
||
57 | */ |
||
58 | private function setKeys() |
||
59 | { |
||
60 | $signerKey = getenv('JWT_SIGNER_KEY'); |
||
61 | // If it's a private key, we also need a public key for validation! |
||
62 | if (file_exists($signerKey)) { |
||
63 | $this->signer = new RsaSha256(); |
||
64 | $password = getenv('JWT_KEY_PASSWORD'); |
||
65 | $this->privateKey = new Key('file://' . $signerKey, $password ?: null); |
||
66 | // We're having an RSA signed key instead of a string |
||
67 | $this->publicKey = new Key('file://' . getenv('JWT_PUBLIC_KEY')); |
||
68 | } else { |
||
69 | $this->signer = new Sha256(); |
||
70 | $this->privateKey = $signerKey; |
||
71 | $this->publicKey = $signerKey; |
||
72 | } |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * JWT is stateless, therefore, we don't support anything but login |
||
77 | * |
||
78 | * @return int |
||
79 | */ |
||
80 | public function supportedServices() |
||
81 | { |
||
82 | return Authenticator::LOGIN | Authenticator::CMS_LOGIN; |
||
83 | } |
||
84 | |||
85 | /** |
||
86 | * @param array $data |
||
87 | * @param HTTPRequest $request |
||
88 | * @param ValidationResult|null $result |
||
89 | * @return Member|null |
||
90 | * @throws \OutOfBoundsException |
||
91 | * @throws \BadMethodCallException |
||
92 | */ |
||
93 | public function authenticate(array $data, HTTPRequest $request, ValidationResult &$result = null) |
||
94 | { |
||
95 | if (!$result) { |
||
96 | $result = new ValidationResult(); |
||
97 | } |
||
98 | $token = $data['token']; |
||
99 | |||
100 | return $this->validateToken($token, $request, $result); |
||
101 | } |
||
102 | |||
103 | /** |
||
104 | * @param Member $member |
||
105 | * @return Token |
||
106 | * @throws ValidationException |
||
107 | * @throws BadMethodCallException |
||
108 | */ |
||
109 | public function generateToken(Member $member) |
||
110 | { |
||
111 | $this->setKeys(); |
||
112 | $config = static::config(); |
||
113 | $uniqueID = uniqid(getenv('JWT_PREFIX'), true); |
||
114 | |||
115 | $request = Controller::curr()->getRequest(); |
||
116 | $audience = $request->getHeader('Origin'); |
||
117 | |||
118 | $builder = new Builder(); |
||
119 | $token = $builder |
||
120 | // Configures the issuer (iss claim) |
||
121 | ->setIssuer(Director::absoluteBaseURL()) |
||
122 | // Configures the audience (aud claim) |
||
123 | ->setAudience($audience) |
||
124 | // Configures the id (jti claim), replicating as a header item |
||
125 | ->setId($uniqueID, true) |
||
126 | // Configures the time that the token was issue (iat claim) |
||
127 | ->setIssuedAt(time()) |
||
128 | // Configures the time that the token can be used (nbf claim) |
||
129 | ->setNotBefore(time() + $config->get('nbf_time')) |
||
130 | // Configures the expiration time of the token (nbf claim) |
||
131 | ->setExpiration(time() + $config->get('nbf_expiration')) |
||
132 | // Configures a new claim, called "uid" |
||
133 | ->set('uid', $member->ID) |
||
134 | // Sign the key with the Signer's key |
||
135 | ->sign($this->signer, $this->privateKey); |
||
136 | |||
137 | // Save the member if it's not anonymous |
||
138 | if ($member->ID > 0) { |
||
139 | $member->JWTUniqueID = $uniqueID; |
||
140 | $member->write(); |
||
141 | } |
||
142 | |||
143 | // Return the token |
||
144 | return $token->getToken(); |
||
145 | } |
||
146 | |||
147 | /** |
||
148 | * @param string $token |
||
149 | * @param HTTPRequest $request |
||
150 | * @param ValidationResult $result |
||
151 | * @return null|Member |
||
152 | * @throws \BadMethodCallException |
||
153 | */ |
||
154 | private function validateToken($token, $request, &$result) |
||
155 | { |
||
156 | $this->setKeys(); |
||
157 | $parser = new Parser(); |
||
158 | $parsedToken = $parser->parse((string)$token); |
||
159 | |||
160 | // Get a validator and the Member for this token |
||
161 | list($validator, $member) = $this->getValidator($request, $parsedToken); |
||
162 | |||
163 | $verified = $parsedToken->verify($this->signer, $this->publicKey); |
||
0 ignored issues
–
show
The method
Lcobucci\JWT\Token::verify() has been deprecated with message: This method will be removed on v4, new validation API should be used
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.
Loading history...
|
|||
164 | $valid = $parsedToken->validate($validator); |
||
0 ignored issues
–
show
The method
Lcobucci\JWT\Token::validate() has been deprecated with message: This method will be removed on v4, new validation API should be used
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.
Loading history...
|
|||
165 | |||
166 | // If the token is not verified, just give up |
||
167 | if (!$verified || !$valid) { |
||
168 | $result->addError('Invalid token'); |
||
169 | } |
||
170 | // An expired token can be renewed |
||
171 | if ( |
||
172 | $verified && |
||
173 | $parsedToken->isExpired() |
||
174 | ) { |
||
175 | $result->addError('Token is expired, please renew your token with a refreshToken query'); |
||
176 | } |
||
177 | // Not entirely fine, do we allow anonymous users? |
||
178 | // Then, if the token is valid, return an anonymous user |
||
179 | if ( |
||
180 | $result->isValid() && |
||
181 | $parsedToken->getClaim('uid') === 0 && |
||
182 | static::config()->get('anonymous_allowed') |
||
183 | ) { |
||
184 | $member = Member::create(['ID' => 0, 'FirstName' => 'Anonymous']); |
||
185 | } |
||
186 | |||
187 | return $result->isValid() ? $member : null; |
||
188 | } |
||
189 | |||
190 | /** |
||
191 | * @param HTTPRequest $request |
||
192 | * @param Token $parsedToken |
||
193 | * @return array[ValidationData, Member] |
||
0 ignored issues
–
show
The doc-type
array[ValidationData, could not be parsed: Expected "]" at position 2, but found "ValidationData". (view supported doc-types)
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.
Loading history...
|
|||
194 | * @throws \OutOfBoundsException |
||
195 | */ |
||
196 | private function getValidator($request, $parsedToken) |
||
197 | { |
||
198 | $audience = $request->getHeader('Origin'); |
||
199 | |||
200 | $member = null; |
||
201 | $id = null; |
||
202 | $validator = new ValidationData(); |
||
0 ignored issues
–
show
The class
Lcobucci\JWT\ValidationData has been deprecated with message: This class will be removed on v4, new validation API should be used
This class, trait or interface has been deprecated. The supplier of the file has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the type will be removed from the class and what other constant to use instead.
Loading history...
|
|||
203 | $validator->setIssuer(Director::absoluteBaseURL()); |
||
204 | $validator->setAudience($audience); |
||
205 | |||
206 | if ($parsedToken->getClaim('uid') === 0 && static::config()->get('anonymous_allowed')) { |
||
0 ignored issues
–
show
The method
Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.
Loading history...
|
|||
207 | $id = $request->getSession()->get('jwt_uid'); |
||
208 | } elseif ($parsedToken->getClaim('uid') > 0) { |
||
0 ignored issues
–
show
The method
Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.
Loading history...
|
|||
209 | $member = Member::get()->byID($parsedToken->getClaim('uid')); |
||
0 ignored issues
–
show
The method
Lcobucci\JWT\Token::getClaim() has been deprecated with message: This method will be removed on v4
This method has been deprecated. The supplier of the class has supplied an explanatory message. The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.
Loading history...
|
|||
210 | $id = $member->JWTUniqueID; |
||
211 | } |
||
212 | |||
213 | $validator->setId($id); |
||
214 | |||
215 | return [$validator, $member]; |
||
216 | } |
||
217 | } |
||
218 |
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.