1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* Created by PhpStorm. |
4
|
|
|
* User: Alexandre |
5
|
|
|
* Date: 10/06/2018 |
6
|
|
|
* Time: 17:53 |
7
|
|
|
*/ |
8
|
|
|
|
9
|
|
|
namespace OAuth2\Endpoints; |
10
|
|
|
|
11
|
|
|
|
12
|
|
|
use GuzzleHttp\Psr7\Response; |
13
|
|
|
use OAuth2\ClientAuthentication\ClientAuthenticationMethodManager; |
14
|
|
|
use OAuth2\Credentials\AccessTokenInterface; |
15
|
|
|
use OAuth2\Credentials\RefreshTokenInterface; |
16
|
|
|
use OAuth2\Credentials\TokenInterface; |
17
|
|
|
use OAuth2\Exceptions\OAuthException; |
18
|
|
|
use OAuth2\Storages\StorageManager; |
19
|
|
|
use OAuth2\Storages\TokenStorageInterface; |
20
|
|
|
use Psr\Http\Message\ResponseInterface; |
21
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
22
|
|
|
|
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* Class TokenRevocationEndpoint |
26
|
|
|
* @package OAuth2\Endpoints |
27
|
|
|
* |
28
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2 |
29
|
|
|
* Implementations MUST support the revocation of refresh tokens and |
30
|
|
|
* SHOULD support the revocation of access tokens (see Implementation |
31
|
|
|
* Note). |
32
|
|
|
* |
33
|
|
|
* The client requests the revocation of a particular token by making an |
34
|
|
|
* HTTP POST request to the token revocation endpoint URL. This URL |
35
|
|
|
* MUST conform to the rules given in [RFC6749], Section 3.1. Clients |
36
|
|
|
* MUST verify that the URL is an HTTPS URL. |
37
|
|
|
* |
38
|
|
|
* The means to obtain the location of the revocation endpoint is out of |
39
|
|
|
* the scope of this specification. For example, the client developer |
40
|
|
|
* may consult the server's documentation or automatic discovery may be |
41
|
|
|
* used. As this endpoint is handling security credentials, the |
42
|
|
|
* endpoint location needs to be obtained from a trustworthy source. |
43
|
|
|
* |
44
|
|
|
* Since requests to the token revocation endpoint result in the |
45
|
|
|
* transmission of plaintext credentials in the HTTP request, URLs for |
46
|
|
|
* token revocation endpoints MUST be HTTPS URLs. The authorization |
47
|
|
|
* server MUST use Transport Layer Security (TLS) [RFC5246] in a version |
48
|
|
|
* compliant with [RFC6749], Section 1.6. Implementations MAY also |
49
|
|
|
* support additional transport-layer security mechanisms that meet |
50
|
|
|
* their security requirements. |
51
|
|
|
* |
52
|
|
|
* If the host of the token revocation endpoint can also be reached over |
53
|
|
|
* HTTP, then the server SHOULD also offer a revocation service at the |
54
|
|
|
* corresponding HTTP URI, but it MUST NOT publish this URI as a token |
55
|
|
|
* revocation endpoint. This ensures that tokens accidentally sent over |
56
|
|
|
* HTTP will be revoked. |
57
|
|
|
*/ |
58
|
|
|
class TokenRevocationEndpoint implements EndpointInterface |
59
|
|
|
{ |
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* @var ClientAuthenticationMethodManager |
63
|
|
|
*/ |
64
|
|
|
private $clientAuthenticationMethodManager; |
65
|
|
|
/** |
66
|
|
|
* @var StorageManager |
67
|
|
|
*/ |
68
|
|
|
private $storageManager; |
69
|
|
|
|
70
|
|
|
/** |
71
|
|
|
* TokenRevocationEndpoint constructor. |
72
|
|
|
* @param ClientAuthenticationMethodManager $clientAuthenticationMethodManager |
73
|
|
|
* @param StorageManager $storageManager |
74
|
|
|
*/ |
75
|
|
|
public function __construct(ClientAuthenticationMethodManager $clientAuthenticationMethodManager, |
76
|
|
|
StorageManager $storageManager) |
77
|
|
|
{ |
78
|
|
|
$this->clientAuthenticationMethodManager = $clientAuthenticationMethodManager; |
79
|
|
|
$this->storageManager = $storageManager; |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* @param ServerRequestInterface $request |
84
|
|
|
* @return ResponseInterface |
85
|
|
|
* |
86
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2.1 |
87
|
|
|
* The client constructs the request by including the following |
88
|
|
|
* parameters using the "application/x-www-form-urlencoded" format in |
89
|
|
|
* the HTTP request entity-body: |
90
|
|
|
* |
91
|
|
|
* token REQUIRED. The token that the client wants to get revoked. |
92
|
|
|
* |
93
|
|
|
* token_type_hint OPTIONAL. A hint about the type of the token |
94
|
|
|
* submitted for revocation. Clients MAY pass this parameter in |
95
|
|
|
* order to help the authorization server to optimize the token |
96
|
|
|
* lookup. If the server is unable to locate the token using |
97
|
|
|
* the given hint, it MUST extend its search across all of its |
98
|
|
|
* supported token types. An authorization server MAY ignore |
99
|
|
|
* this parameter, particularly if it is able to detect the |
100
|
|
|
* token type automatically. This specification defines two |
101
|
|
|
* such values: |
102
|
|
|
* access_token: An access token as defined in [RFC6749], |
103
|
|
|
* Section 1.4 |
104
|
|
|
* refresh_token: A refresh token as defined in [RFC6749], |
105
|
|
|
* Section 1.5 |
106
|
|
|
* |
107
|
|
|
* Specific implementations, profiles, and extensions of this |
108
|
|
|
* specification MAY define other values for this parameter |
109
|
|
|
* using the registry defined in Section 4.1.2. |
110
|
|
|
* |
111
|
|
|
* The client also includes its authentication credentials as described |
112
|
|
|
* in Section 2.3. of [RFC6749]. |
113
|
|
|
* |
114
|
|
|
* For example, a client may request the revocation of a refresh token |
115
|
|
|
* with the following request: |
116
|
|
|
* |
117
|
|
|
* POST /revoke HTTP/1.1 |
118
|
|
|
* Host: server.example.com |
119
|
|
|
* Content-Type: application/x-www-form-urlencoded |
120
|
|
|
* Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW |
121
|
|
|
* |
122
|
|
|
* token=45ghiukldjahdnhzdauz&token_type_hint=refresh_token |
123
|
|
|
*/ |
124
|
|
|
public function handleRequest(ServerRequestInterface $request): ResponseInterface |
125
|
|
|
{ |
126
|
|
|
if ($request->getMethod() !== 'POST') { |
127
|
|
|
return new Response(405); |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
$requestData = $request->getParsedBody(); |
131
|
|
|
$token = $requestData['token'] ?? null; |
132
|
|
|
$tokenTypeHint = $requestData['token_type_hint'] ?? null; |
133
|
|
|
|
134
|
|
|
try { |
135
|
|
|
/** |
136
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2.1 |
137
|
|
|
* The authorization server first validates the client credentials (in |
138
|
|
|
* case of a confidential client) and then verifies whether the token |
139
|
|
|
* was issued to the client making the revocation request. If this |
140
|
|
|
* validation fails, the request is refused and the client is informed |
141
|
|
|
* of the error by the authorization server as described below. |
142
|
|
|
*/ |
143
|
|
|
$client = $this->clientAuthenticationMethodManager->authenticate($request, $requestData); |
144
|
|
|
|
145
|
|
|
if (!$token) { |
146
|
|
|
throw new OAuthException('invalid_request', |
147
|
|
|
'The token that the client wants to get revoked is required.', |
148
|
|
|
'https://tools.ietf.org/html/rfc7009#section-2.1'); |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
$token = $this->findToken($token, $tokenTypeHint); |
152
|
|
|
|
153
|
|
|
if ($token->getClientIdentifier() !== $client->getIdentifier()) { |
154
|
|
|
throw new OAuthException('unauthorized_client', |
155
|
|
|
'The token that the client wants to get revoked was not issued to it.', |
156
|
|
|
'https://tools.ietf.org/html/rfc7009#section-2.1'); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2.1 |
161
|
|
|
* In the next step, the authorization server invalidates the token. |
162
|
|
|
* The invalidation takes place immediately, and the token cannot be |
163
|
|
|
* used again after the revocation. In practice, there could be a |
164
|
|
|
* propagation delay, for example, in which some servers know about the |
165
|
|
|
* invalidation while others do not. Implementations should minimize |
166
|
|
|
* that window, and clients must not try to use the token after receipt |
167
|
|
|
* of an HTTP 200 response from the server. |
168
|
|
|
*/ |
169
|
|
|
$this->revokeToken($token); |
170
|
|
|
} catch (OAuthException $e) { |
171
|
|
|
$status = 400; |
172
|
|
|
$headers = ['Content-Type' => 'application/json']; |
173
|
|
|
if ($e->getError() === 'invalid_client') { |
174
|
|
|
$status = 401; |
175
|
|
|
if ($request->hasHeader('Authorization')) { |
176
|
|
|
$headers['WWW-Authenticate'] = 'Basic'; |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
return new Response($status, $headers, $e->jsonSerialize()); |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2.2 |
184
|
|
|
* The authorization server responds with HTTP status code 200 if the |
185
|
|
|
* token has been revoked successfully or if the client submitted an |
186
|
|
|
* invalid token. |
187
|
|
|
* |
188
|
|
|
* Note: invalid tokens do not cause an error response since the client |
189
|
|
|
* cannot handle such an error in a reasonable way. Moreover, the |
190
|
|
|
* purpose of the revocation request, invalidating the particular token, |
191
|
|
|
* is already achieved. |
192
|
|
|
* |
193
|
|
|
* The content of the response body is ignored by the client as all |
194
|
|
|
* necessary information is conveyed in the response code. |
195
|
|
|
* |
196
|
|
|
* An invalid token type hint value is ignored by the authorization |
197
|
|
|
* server and does not influence the revocation response. |
198
|
|
|
*/ |
199
|
|
|
return new Response(200); |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* @return TokenStorageInterface[] |
204
|
|
|
*/ |
205
|
|
|
protected function getStorages(): array |
206
|
|
|
{ |
207
|
|
|
return [ |
208
|
|
|
'access_token' => $this->storageManager->getAccessTokenStorage(), |
209
|
|
|
'refresh_token' => $this->storageManager->getRefreshTokenStorage() |
210
|
|
|
]; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
protected function findToken(string $token, ?string $tokenTypeHint = null): ?TokenInterface |
214
|
|
|
{ |
215
|
|
|
$storages = $this->getStorages(); |
216
|
|
|
|
217
|
|
|
if ($tokenTypeHint && isset($storages[$tokenTypeHint])) { |
218
|
|
|
$storage = $storages[$tokenTypeHint]; |
219
|
|
|
unset($storages[$tokenTypeHint]); |
220
|
|
|
if ($tokenFound = $storage->get($token)) { |
221
|
|
|
return $tokenFound; |
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
225
|
|
|
foreach ($storages as $storage) { |
226
|
|
|
if ($tokenFound = $storage->get($token)) { |
227
|
|
|
return $tokenFound; |
228
|
|
|
} |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return null; |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* @param TokenInterface $token |
236
|
|
|
* |
237
|
|
|
* @see https://tools.ietf.org/html/rfc7009#section-2.1 |
238
|
|
|
* Depending on the authorization server's revocation policy, the |
239
|
|
|
* revocation of a particular token may cause the revocation of related |
240
|
|
|
* tokens and the underlying authorization grant. If the particular |
241
|
|
|
* token is a refresh token and the authorization server supports the |
242
|
|
|
* revocation of access tokens, then the authorization server SHOULD |
243
|
|
|
* also invalidate all access tokens based on the same authorization |
244
|
|
|
* grant (see Implementation Note). If the token passed to the request |
245
|
|
|
* is an access token, the server MAY revoke the respective refresh |
246
|
|
|
* token as well. |
247
|
|
|
*/ |
248
|
|
|
protected function revokeToken(TokenInterface $token) |
249
|
|
|
{ |
250
|
|
|
$accessTokenStorage = $this->storageManager->getAccessTokenStorage(); |
251
|
|
|
$refreshTokenStorage = $this->storageManager->getRefreshTokenStorage(); |
252
|
|
|
|
253
|
|
|
if ($token instanceof AccessTokenInterface) { |
254
|
|
|
$accessTokenStorage->revoke($token); |
255
|
|
|
if ($refreshToken = $token->getRefreshToken()) { |
256
|
|
|
if ($refreshToken = $refreshTokenStorage->get($refreshToken)) { |
257
|
|
|
$refreshTokenStorage->revoke($refreshToken); |
258
|
|
|
} |
259
|
|
|
} |
260
|
|
|
} else if ($token instanceof RefreshTokenInterface) { |
261
|
|
|
$refreshTokenStorage->revoke($token); |
262
|
|
|
foreach ($this->storageManager->getAccessTokenStorage()->getByRefreshToken($token) as $accessToken) { |
263
|
|
|
$accessTokenStorage->revoke($accessToken); |
264
|
|
|
} |
265
|
|
|
} |
266
|
|
|
} |
267
|
|
|
} |