Complex classes like AuthCodeGrant often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
While breaking up the class, it is a good idea to analyze how other classes use AuthCodeGrant, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class AuthCodeGrant extends AbstractAuthorizeGrant |
||
32 | { |
||
33 | /** |
||
34 | * @var DateInterval |
||
35 | */ |
||
36 | private $authCodeTTL; |
||
37 | |||
38 | /** |
||
39 | * @var bool |
||
40 | */ |
||
41 | private $requireCodeChallengeForPublicClients = true; |
||
42 | |||
43 | /** |
||
44 | * @var CodeChallengeVerifierInterface[] |
||
45 | */ |
||
46 | private $codeChallengeVerifiers = []; |
||
47 | |||
48 | /** |
||
49 | * @param AuthCodeRepositoryInterface $authCodeRepository |
||
50 | * @param RefreshTokenRepositoryInterface $refreshTokenRepository |
||
51 | * @param DateInterval $authCodeTTL |
||
52 | * |
||
53 | * @throws Exception |
||
54 | */ |
||
55 | 44 | public function __construct( |
|
73 | |||
74 | /** |
||
75 | * Disable the requirement for a code challenge for public clients. |
||
76 | */ |
||
77 | public function disableRequireCodeChallengeForPublicClients() |
||
81 | |||
82 | /** |
||
83 | * Respond to an access token request. |
||
84 | * |
||
85 | * @param ServerRequestInterface $request |
||
86 | * @param ResponseTypeInterface $responseType |
||
87 | * @param DateInterval $accessTokenTTL |
||
88 | * |
||
89 | * @throws OAuthServerException |
||
90 | * |
||
91 | * @return ResponseTypeInterface |
||
92 | */ |
||
93 | 21 | public function respondToAccessTokenRequest( |
|
94 | ServerRequestInterface $request, |
||
95 | ResponseTypeInterface $responseType, |
||
96 | DateInterval $accessTokenTTL |
||
97 | ) { |
||
98 | 21 | list($clientId) = $this->getClientCredentials($request); |
|
99 | |||
100 | 21 | $client = $this->getClientEntityOrFail($clientId, $request); |
|
101 | |||
102 | // Only validate the client if it is confidential |
||
103 | 21 | if ($client->isConfidential()) { |
|
104 | 14 | $this->validateClient($request); |
|
105 | } |
||
106 | |||
107 | 21 | $encryptedAuthCode = $this->getRequestParameter('code', $request, null); |
|
108 | |||
109 | 21 | if ($encryptedAuthCode === null) { |
|
110 | 1 | throw OAuthServerException::invalidRequest('code'); |
|
111 | } |
||
112 | |||
113 | try { |
||
114 | 20 | $authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); |
|
115 | |||
116 | 19 | $this->validateAuthorizationCode($authCodePayload, $client, $request); |
|
117 | |||
118 | 14 | $scopes = $this->scopeRepository->finalizeScopes( |
|
119 | 14 | $this->validateScopes($authCodePayload->scopes), |
|
120 | 14 | $this->getIdentifier(), |
|
121 | $client, |
||
122 | 14 | $authCodePayload->user_id |
|
123 | ); |
||
124 | 6 | } catch (LogicException $e) { |
|
125 | 1 | throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code', $e); |
|
126 | } |
||
127 | |||
128 | // Validate code challenge |
||
129 | 14 | if (!empty($authCodePayload->code_challenge)) { |
|
130 | 7 | $codeVerifier = $this->getRequestParameter('code_verifier', $request, null); |
|
131 | |||
132 | 7 | if ($codeVerifier === null) { |
|
133 | 1 | throw OAuthServerException::invalidRequest('code_verifier'); |
|
134 | } |
||
135 | |||
136 | // Validate code_verifier according to RFC-7636 |
||
137 | // @see: https://tools.ietf.org/html/rfc7636#section-4.1 |
||
138 | 6 | if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeVerifier) !== 1) { |
|
139 | 3 | throw OAuthServerException::invalidRequest( |
|
140 | 3 | 'code_verifier', |
|
141 | 3 | 'Code Verifier must follow the specifications of RFC-7636.' |
|
142 | ); |
||
143 | } |
||
144 | |||
145 | 3 | if (property_exists($authCodePayload, 'code_challenge_method')) { |
|
146 | 3 | if (isset($this->codeChallengeVerifiers[$authCodePayload->code_challenge_method])) { |
|
147 | 3 | $codeChallengeVerifier = $this->codeChallengeVerifiers[$authCodePayload->code_challenge_method]; |
|
148 | |||
149 | 3 | if ($codeChallengeVerifier->verifyCodeChallenge($codeVerifier, $authCodePayload->code_challenge) === false) { |
|
150 | 3 | throw OAuthServerException::invalidGrant('Failed to verify `code_verifier`.'); |
|
151 | } |
||
152 | } else { |
||
153 | throw OAuthServerException::serverError( |
||
154 | sprintf( |
||
155 | 'Unsupported code challenge method `%s`', |
||
156 | $authCodePayload->code_challenge_method |
||
157 | ) |
||
158 | ); |
||
159 | } |
||
160 | } |
||
161 | } |
||
162 | |||
163 | // Issue and persist new access token |
||
164 | 9 | $accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); |
|
165 | 9 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::ACCESS_TOKEN_ISSUED, $request)); |
|
166 | 9 | $responseType->setAccessToken($accessToken); |
|
|
|||
167 | |||
168 | // Issue and persist new refresh token if given |
||
169 | 9 | $refreshToken = $this->issueRefreshToken($accessToken); |
|
170 | |||
171 | 7 | if ($refreshToken !== null) { |
|
172 | 6 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::REFRESH_TOKEN_ISSUED, $request)); |
|
173 | 6 | $responseType->setRefreshToken($refreshToken); |
|
174 | } |
||
175 | |||
176 | // Revoke used auth code |
||
177 | 7 | $this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); |
|
178 | |||
179 | 7 | return $responseType; |
|
180 | } |
||
181 | |||
182 | /** |
||
183 | * Validate the authorization code. |
||
184 | * |
||
185 | * @param stdClass $authCodePayload |
||
186 | * @param ClientEntityInterface $client |
||
187 | * @param ServerRequestInterface $request |
||
188 | */ |
||
189 | 19 | private function validateAuthorizationCode( |
|
216 | |||
217 | /** |
||
218 | * Return the grant identifier that can be used in matching up requests. |
||
219 | * |
||
220 | * @return string |
||
221 | */ |
||
222 | 32 | public function getIdentifier() |
|
226 | |||
227 | /** |
||
228 | * {@inheritdoc} |
||
229 | */ |
||
230 | 3 | public function canRespondToAuthorizationRequest(ServerRequestInterface $request) |
|
238 | |||
239 | /** |
||
240 | * {@inheritdoc} |
||
241 | */ |
||
242 | 14 | public function validateAuthorizationRequest(ServerRequestInterface $request) |
|
243 | { |
||
244 | 14 | $clientId = $this->getQueryStringParameter( |
|
245 | 14 | 'client_id', |
|
246 | $request, |
||
247 | 14 | $this->getServerParameter('PHP_AUTH_USER', $request) |
|
248 | ); |
||
249 | |||
250 | 14 | if ($clientId === null) { |
|
251 | 1 | throw OAuthServerException::invalidRequest('client_id'); |
|
252 | } |
||
253 | |||
254 | 13 | $client = $this->getClientEntityOrFail($clientId, $request); |
|
255 | |||
256 | 12 | $redirectUri = $this->getQueryStringParameter('redirect_uri', $request); |
|
257 | |||
258 | 12 | if ($redirectUri !== null) { |
|
259 | 10 | $this->validateRedirectUri($redirectUri, $client, $request); |
|
260 | 2 | } elseif (empty($client->getRedirectUri()) || |
|
261 | 2 | (\is_array($client->getRedirectUri()) && \count($client->getRedirectUri()) !== 1)) { |
|
262 | 1 | $this->getEmitter()->emit(new RequestEvent(RequestEvent::CLIENT_AUTHENTICATION_FAILED, $request)); |
|
263 | 1 | throw OAuthServerException::invalidClient($request); |
|
264 | } else { |
||
265 | 1 | $redirectUri = \is_array($client->getRedirectUri()) |
|
266 | ? $client->getRedirectUri()[0] |
||
267 | 1 | : $client->getRedirectUri(); |
|
268 | } |
||
269 | |||
270 | 9 | $scopes = $this->validateScopes( |
|
271 | 9 | $this->getQueryStringParameter('scope', $request, $this->defaultScope), |
|
272 | $redirectUri |
||
273 | ); |
||
274 | |||
275 | 9 | $stateParameter = $this->getQueryStringParameter('state', $request); |
|
276 | |||
277 | 9 | $authorizationRequest = new AuthorizationRequest(); |
|
278 | 9 | $authorizationRequest->setGrantTypeId($this->getIdentifier()); |
|
279 | 9 | $authorizationRequest->setClient($client); |
|
280 | 9 | $authorizationRequest->setRedirectUri($redirectUri); |
|
281 | |||
282 | 9 | if ($stateParameter !== null) { |
|
283 | $authorizationRequest->setState($stateParameter); |
||
284 | } |
||
285 | |||
286 | 9 | $authorizationRequest->setScopes($scopes); |
|
287 | |||
288 | 9 | $codeChallenge = $this->getQueryStringParameter('code_challenge', $request); |
|
289 | |||
290 | 9 | if ($codeChallenge !== null) { |
|
291 | 5 | $codeChallengeMethod = $this->getQueryStringParameter('code_challenge_method', $request, 'plain'); |
|
292 | |||
293 | 5 | if (array_key_exists($codeChallengeMethod, $this->codeChallengeVerifiers) === false) { |
|
294 | 1 | throw OAuthServerException::invalidRequest( |
|
295 | 1 | 'code_challenge_method', |
|
296 | 1 | 'Code challenge method must be one of ' . implode(', ', array_map( |
|
297 | function ($method) { |
||
298 | 1 | return '`' . $method . '`'; |
|
299 | 1 | }, |
|
300 | 1 | array_keys($this->codeChallengeVerifiers) |
|
301 | )) |
||
302 | ); |
||
303 | } |
||
304 | |||
305 | // Validate code_challenge according to RFC-7636 |
||
306 | // @see: https://tools.ietf.org/html/rfc7636#section-4.2 |
||
307 | 4 | if (preg_match('/^[A-Za-z0-9-._~]{43,128}$/', $codeChallenge) !== 1) { |
|
308 | 3 | throw OAuthServerException::invalidRequest( |
|
309 | 3 | 'code_challenged', |
|
310 | 3 | 'Code challenge must follow the specifications of RFC-7636.' |
|
311 | ); |
||
312 | } |
||
313 | |||
314 | 1 | $authorizationRequest->setCodeChallenge($codeChallenge); |
|
315 | 1 | $authorizationRequest->setCodeChallengeMethod($codeChallengeMethod); |
|
316 | 4 | } elseif ($this->requireCodeChallengeForPublicClients && !$client->isConfidential()) { |
|
317 | 1 | throw OAuthServerException::invalidRequest('code_challenge', 'Code challenge must be provided for public clients'); |
|
318 | } |
||
319 | |||
320 | 4 | return $authorizationRequest; |
|
321 | } |
||
322 | |||
323 | /** |
||
324 | * {@inheritdoc} |
||
325 | */ |
||
326 | 7 | public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) |
|
387 | |||
388 | /** |
||
389 | * Get the client redirect URI if not set in the request. |
||
390 | * |
||
391 | * @param AuthorizationRequest $authorizationRequest |
||
392 | * |
||
393 | * @return string |
||
394 | */ |
||
395 | 6 | private function getClientRedirectUri(AuthorizationRequest $authorizationRequest) |
|
401 | } |
||
402 |
Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code: