1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* @author Alex Bilbie <[email protected]> |
4
|
|
|
* @copyright Copyright (c) Alex Bilbie |
5
|
|
|
* @license http://mit-license.org/ |
6
|
|
|
* |
7
|
|
|
* @link https://github.com/thephpleague/oauth2-server |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace League\OAuth2\Server\Grant; |
11
|
|
|
|
12
|
|
|
use DateInterval; |
13
|
|
|
use League\OAuth2\Server\Entities\ClientEntityInterface; |
14
|
|
|
use League\OAuth2\Server\Entities\ScopeEntityInterface; |
15
|
|
|
use League\OAuth2\Server\Entities\UserEntityInterface; |
16
|
|
|
use League\OAuth2\Server\Exception\OAuthServerException; |
17
|
|
|
use League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface; |
18
|
|
|
use League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface; |
19
|
|
|
use League\OAuth2\Server\RequestEvent; |
20
|
|
|
use League\OAuth2\Server\RequestTypes\AuthorizationRequest; |
21
|
|
|
use League\OAuth2\Server\ResponseTypes\RedirectResponse; |
22
|
|
|
use League\OAuth2\Server\ResponseTypes\ResponseTypeInterface; |
23
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
24
|
|
|
|
25
|
|
|
class AuthCodeGrant extends AbstractAuthorizeGrant |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* @var \DateInterval |
29
|
|
|
*/ |
30
|
|
|
private $authCodeTTL; |
31
|
|
|
|
32
|
|
|
/** |
33
|
|
|
* @param \League\OAuth2\Server\Repositories\AuthCodeRepositoryInterface $authCodeRepository |
34
|
|
|
* @param \League\OAuth2\Server\Repositories\RefreshTokenRepositoryInterface $refreshTokenRepository |
35
|
|
|
* @param \DateInterval $authCodeTTL |
36
|
|
|
*/ |
37
|
|
|
public function __construct( |
38
|
|
|
AuthCodeRepositoryInterface $authCodeRepository, |
39
|
|
|
RefreshTokenRepositoryInterface $refreshTokenRepository, |
40
|
|
|
\DateInterval $authCodeTTL |
41
|
|
|
) { |
42
|
|
|
$this->setAuthCodeRepository($authCodeRepository); |
43
|
|
|
$this->setRefreshTokenRepository($refreshTokenRepository); |
44
|
|
|
$this->authCodeTTL = $authCodeTTL; |
45
|
|
|
$this->refreshTokenTTL = new \DateInterval('P1M'); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* Respond to an access token request. |
50
|
|
|
* |
51
|
|
|
* @param \Psr\Http\Message\ServerRequestInterface $request |
52
|
|
|
* @param \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface $responseType |
53
|
|
|
* @param \DateInterval $accessTokenTTL |
54
|
|
|
* |
55
|
|
|
* @throws \League\OAuth2\Server\Exception\OAuthServerException |
56
|
|
|
* |
57
|
|
|
* @return \League\OAuth2\Server\ResponseTypes\ResponseTypeInterface |
58
|
|
|
*/ |
59
|
|
|
public function respondToAccessTokenRequest( |
60
|
|
|
ServerRequestInterface $request, |
61
|
|
|
ResponseTypeInterface $responseType, |
62
|
|
|
DateInterval $accessTokenTTL |
63
|
|
|
) { |
64
|
|
|
// Validate request |
65
|
|
|
$client = $this->validateClient($request); |
66
|
|
|
$encryptedAuthCode = $this->getRequestParameter('code', $request, null); |
67
|
|
|
|
68
|
|
|
if ($encryptedAuthCode === null) { |
69
|
|
|
throw OAuthServerException::invalidRequest('code'); |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
// Validate the authorization code |
73
|
|
|
try { |
74
|
|
|
$authCodePayload = json_decode($this->decrypt($encryptedAuthCode)); |
75
|
|
|
if (time() > $authCodePayload->expire_time) { |
76
|
|
|
throw OAuthServerException::invalidRequest('code', 'Authorization code has expired'); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
if ($this->authCodeRepository->isAuthCodeRevoked($authCodePayload->auth_code_id) === true) { |
80
|
|
|
throw OAuthServerException::invalidRequest('code', 'Authorization code has been revoked'); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
if ($authCodePayload->client_id !== $client->getIdentifier()) { |
84
|
|
|
throw OAuthServerException::invalidRequest('code', 'Authorization code was not issued to this client'); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
// The redirect URI is required in this request |
88
|
|
|
$redirectUri = $this->getRequestParameter('redirect_uri', $request, null); |
89
|
|
|
if (empty($authCodePayload->redirect_uri) === false && $redirectUri === null) { |
90
|
|
|
throw OAuthServerException::invalidRequest('redirect_uri'); |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
if ($authCodePayload->redirect_uri !== $redirectUri) { |
94
|
|
|
throw OAuthServerException::invalidRequest('redirect_uri', 'Invalid redirect URI'); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
$scopes = []; |
98
|
|
|
foreach ($authCodePayload->scopes as $scopeId) { |
99
|
|
|
$scope = $this->scopeRepository->getScopeEntityByIdentifier($scopeId); |
100
|
|
|
|
101
|
|
|
if (!$scope instanceof ScopeEntityInterface) { |
102
|
|
|
// @codeCoverageIgnoreStart |
103
|
|
|
throw OAuthServerException::invalidScope($scopeId); |
104
|
|
|
// @codeCoverageIgnoreEnd |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
$scopes[] = $scope; |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
// Finalize the requested scopes |
111
|
|
|
$scopes = $this->scopeRepository->finalizeScopes($scopes, $this->getIdentifier(), $client, $authCodePayload->user_id); |
112
|
|
|
} catch (\LogicException $e) { |
113
|
|
|
throw OAuthServerException::invalidRequest('code', 'Cannot decrypt the authorization code'); |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
// Issue and persist access + refresh tokens |
117
|
|
|
$accessToken = $this->issueAccessToken($accessTokenTTL, $client, $authCodePayload->user_id, $scopes); |
118
|
|
|
$refreshToken = $this->issueRefreshToken($accessToken); |
119
|
|
|
|
120
|
|
|
// Inject tokens into response type |
121
|
|
|
$responseType->setAccessToken($accessToken); |
122
|
|
|
$responseType->setRefreshToken($refreshToken); |
123
|
|
|
|
124
|
|
|
// Revoke used auth code |
125
|
|
|
$this->authCodeRepository->revokeAuthCode($authCodePayload->auth_code_id); |
126
|
|
|
|
127
|
|
|
return $responseType; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Return the grant identifier that can be used in matching up requests. |
132
|
|
|
* |
133
|
|
|
* @return string |
134
|
|
|
*/ |
135
|
|
|
public function getIdentifier() |
136
|
|
|
{ |
137
|
|
|
return 'authorization_code'; |
138
|
|
|
} |
139
|
|
|
|
140
|
|
|
/** |
141
|
|
|
* {@inheritdoc} |
142
|
|
|
*/ |
143
|
|
|
public function canRespondToAuthorizationRequest(ServerRequestInterface $request) |
144
|
|
|
{ |
145
|
|
|
return ( |
146
|
|
|
array_key_exists('response_type', $request->getQueryParams()) |
147
|
|
|
&& $request->getQueryParams()['response_type'] === 'code' |
148
|
|
|
&& isset($request->getQueryParams()['client_id']) |
149
|
|
|
); |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* {@inheritdoc} |
154
|
|
|
*/ |
155
|
|
|
public function validateAuthorizationRequest(ServerRequestInterface $request) |
156
|
|
|
{ |
157
|
|
|
$clientId = $this->getQueryStringParameter( |
158
|
|
|
'client_id', |
159
|
|
|
$request, |
160
|
|
|
$this->getServerParameter('PHP_AUTH_USER', $request) |
161
|
|
|
); |
162
|
|
|
if (is_null($clientId)) { |
163
|
|
|
throw OAuthServerException::invalidRequest('client_id'); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$client = $this->clientRepository->getClientEntity( |
167
|
|
|
$clientId, |
168
|
|
|
$this->getIdentifier() |
169
|
|
|
); |
170
|
|
|
|
171
|
|
|
if ($client instanceof ClientEntityInterface === false) { |
172
|
|
|
$this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); |
173
|
|
|
throw OAuthServerException::invalidClient(); |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
$redirectUri = $this->getQueryStringParameter('redirect_uri', $request); |
177
|
|
|
if ($redirectUri !== null) { |
178
|
|
|
if ( |
179
|
|
|
is_string($client->getRedirectUri()) |
180
|
|
|
&& (strcmp($client->getRedirectUri(), $redirectUri) !== 0) |
181
|
|
|
) { |
182
|
|
|
$this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); |
183
|
|
|
throw OAuthServerException::invalidClient(); |
184
|
|
|
} elseif ( |
185
|
|
|
is_array($client->getRedirectUri()) |
186
|
|
|
&& in_array($redirectUri, $client->getRedirectUri()) === false |
187
|
|
|
) { |
188
|
|
|
$this->getEmitter()->emit(new RequestEvent('client.authentication.failed', $request)); |
189
|
|
|
throw OAuthServerException::invalidClient(); |
190
|
|
|
} |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
$scopes = $this->validateScopes( |
194
|
|
|
$this->getQueryStringParameter('scope', $request), |
195
|
|
|
$client->getRedirectUri() |
|
|
|
|
196
|
|
|
); |
197
|
|
|
|
198
|
|
|
$stateParameter = $this->getQueryStringParameter('state', $request); |
199
|
|
|
|
200
|
|
|
$authorizationRequest = new AuthorizationRequest(); |
201
|
|
|
$authorizationRequest->setGrantTypeId($this->getIdentifier()); |
202
|
|
|
$authorizationRequest->setClient($client); |
203
|
|
|
$authorizationRequest->setRedirectUri($redirectUri); |
204
|
|
|
$authorizationRequest->setState($stateParameter); |
205
|
|
|
$authorizationRequest->setScopes($scopes); |
206
|
|
|
|
207
|
|
|
return $authorizationRequest; |
208
|
|
|
} |
209
|
|
|
|
210
|
|
|
/** |
211
|
|
|
* {@inheritdoc} |
212
|
|
|
*/ |
213
|
|
|
public function completeAuthorizationRequest(AuthorizationRequest $authorizationRequest) |
214
|
|
|
{ |
215
|
|
|
if ($authorizationRequest->getUser() instanceof UserEntityInterface === false) { |
216
|
|
|
throw new \LogicException('An instance of UserEntityInterface should be set on the AuthorizationRequest'); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
$finalRedirectUri = ($authorizationRequest->getRedirectUri() === null) |
220
|
|
|
? is_array($authorizationRequest->getClient()->getRedirectUri()) |
221
|
|
|
? $authorizationRequest->getClient()->getRedirectUri()[0] |
222
|
|
|
: $authorizationRequest->getClient()->getRedirectUri() |
223
|
|
|
: $authorizationRequest->getRedirectUri(); |
224
|
|
|
|
225
|
|
|
// The user approved the client, redirect them back with an auth code |
226
|
|
|
if ($authorizationRequest->isAuthorizationApproved() === true) { |
227
|
|
|
$authCode = $this->issueAuthCode( |
228
|
|
|
$this->authCodeTTL, |
229
|
|
|
$authorizationRequest->getClient(), |
230
|
|
|
$authorizationRequest->getUser()->getIdentifier(), |
231
|
|
|
$authorizationRequest->getRedirectUri(), |
232
|
|
|
$authorizationRequest->getScopes() |
233
|
|
|
); |
234
|
|
|
|
235
|
|
|
$redirectPayload['code'] = $this->encrypt( |
|
|
|
|
236
|
|
|
json_encode( |
237
|
|
|
[ |
238
|
|
|
'client_id' => $authCode->getClient()->getIdentifier(), |
239
|
|
|
'redirect_uri' => $authCode->getRedirectUri(), |
240
|
|
|
'auth_code_id' => $authCode->getIdentifier(), |
241
|
|
|
'scopes' => $authCode->getScopes(), |
242
|
|
|
'user_id' => $authCode->getUserIdentifier(), |
243
|
|
|
'expire_time' => (new \DateTime())->add($this->authCodeTTL)->format('U'), |
244
|
|
|
] |
245
|
|
|
) |
246
|
|
|
); |
247
|
|
|
$redirectPayload['state'] = $authorizationRequest->getState(); |
248
|
|
|
|
249
|
|
|
$response = new RedirectResponse(); |
250
|
|
|
$response->setRedirectUri( |
251
|
|
|
$this->makeRedirectUri( |
252
|
|
|
$finalRedirectUri, |
253
|
|
|
$redirectPayload |
254
|
|
|
) |
255
|
|
|
); |
256
|
|
|
|
257
|
|
|
return $response; |
258
|
|
|
} |
259
|
|
|
|
260
|
|
|
// The user denied the client, redirect them back with an error |
261
|
|
|
throw OAuthServerException::accessDenied( |
262
|
|
|
'The user denied the request', |
263
|
|
|
$finalRedirectUri |
264
|
|
|
); |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
|
This check looks at variables that are passed out again to other methods.
If the outgoing method call has stricter type requirements than the method itself, an issue is raised.
An additional type check may prevent trouble.