|
1
|
|
|
<?php |
|
2
|
|
|
/** |
|
3
|
|
|
* Created by PhpStorm. |
|
4
|
|
|
* User: Alexandre |
|
5
|
|
|
* Date: 18/02/2018 |
|
6
|
|
|
* Time: 18:14 |
|
7
|
|
|
*/ |
|
8
|
|
|
|
|
9
|
|
|
namespace OAuth2\Endpoints; |
|
10
|
|
|
|
|
11
|
|
|
|
|
12
|
|
|
use GuzzleHttp\Psr7\Response; |
|
13
|
|
|
use OAuth2\Endpoints\Authorization\AuthorizationRequest; |
|
14
|
|
|
use OAuth2\Endpoints\Authorization\AuthorizationRequestBuilder; |
|
15
|
|
|
use OAuth2\Endpoints\Authorization\AuthorizationRequestInterface; |
|
16
|
|
|
use OAuth2\Exceptions\InvalidAuthorizationRequest; |
|
17
|
|
|
use OAuth2\Exceptions\InvalidRequestMethod; |
|
18
|
|
|
use OAuth2\Exceptions\OAuthException; |
|
19
|
|
|
use OAuth2\Roles\AuthorizationServer\EndUserInterface; |
|
20
|
|
|
use Psr\Http\Message\ResponseInterface; |
|
21
|
|
|
use Psr\Http\Message\ServerRequestInterface; |
|
22
|
|
|
|
|
23
|
|
|
|
|
24
|
|
|
/** |
|
25
|
|
|
* Class AuthorizationEndpoint |
|
26
|
|
|
* @package OAuth2\Endpoints |
|
27
|
|
|
* |
|
28
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-3.1 |
|
29
|
|
|
* The authorization endpoint is used to interact with the resource |
|
30
|
|
|
* owner and obtain an authorization grant. The authorization server |
|
31
|
|
|
* MUST first verify the identity of the resource owner. The way in |
|
32
|
|
|
* which the authorization server authenticates the resource owner |
|
33
|
|
|
* (e.g., username and password login, session cookies) is beyond the |
|
34
|
|
|
* scope of this specification. |
|
35
|
|
|
* |
|
36
|
|
|
* The means through which the client obtains the location of the |
|
37
|
|
|
* authorization endpoint are beyond the scope of this specification, |
|
38
|
|
|
* but the location is typically provided in the service documentation. |
|
39
|
|
|
* |
|
40
|
|
|
* The endpoint URI MAY include an "application/x-www-form-urlencoded" |
|
41
|
|
|
* formatted (per Appendix B) query component ([RFC3986] Section 3.4), |
|
42
|
|
|
* which MUST be retained when adding additional query parameters. The |
|
43
|
|
|
* endpoint URI MUST NOT include a fragment component. |
|
44
|
|
|
* |
|
45
|
|
|
* Since requests to the authorization endpoint result in user |
|
46
|
|
|
* authentication and the transmission of clear-text credentials (in the |
|
47
|
|
|
* HTTP response), the authorization server MUST require the use of TLS |
|
48
|
|
|
* as described in Section 1.6 when sending requests to the |
|
49
|
|
|
* authorization endpoint. |
|
50
|
|
|
* |
|
51
|
|
|
* The authorization server MUST support the use of the HTTP "GET" |
|
52
|
|
|
* method [RFC2616] for the authorization endpoint and MAY support the |
|
53
|
|
|
* use of the "POST" method as well. |
|
54
|
|
|
* |
|
55
|
|
|
* Parameters sent without a value MUST be treated as if they were |
|
56
|
|
|
* omitted from the request. The authorization server MUST ignore |
|
57
|
|
|
* unrecognized request parameters. Request and response parameters |
|
58
|
|
|
* MUST NOT be included more than once. |
|
59
|
|
|
*/ |
|
60
|
|
|
class AuthorizationEndpoint implements EndpointInterface |
|
61
|
|
|
{ |
|
62
|
|
|
/** |
|
63
|
|
|
* @var AuthorizationRequestBuilder |
|
64
|
|
|
*/ |
|
65
|
|
|
private $authorizationRequestBuilder; |
|
66
|
|
|
/** |
|
67
|
|
|
* @var AuthorizationRequest|null |
|
68
|
|
|
*/ |
|
69
|
|
|
private $authorizationRequest = null; |
|
70
|
|
|
/** |
|
71
|
|
|
* @var EndUserInterface |
|
72
|
|
|
*/ |
|
73
|
|
|
private $authorizationServerEndUser; |
|
74
|
|
|
|
|
75
|
|
|
public function __construct(AuthorizationRequestBuilder $authorizationRequestBuilder, |
|
76
|
|
|
EndUserInterface $authorizationServerEndUser) |
|
77
|
|
|
{ |
|
78
|
|
|
$this->authorizationRequestBuilder = $authorizationRequestBuilder; |
|
79
|
|
|
$this->authorizationServerEndUser = $authorizationServerEndUser; |
|
80
|
|
|
} |
|
81
|
|
|
|
|
82
|
|
|
public function verify(ServerRequestInterface $request): ?ResponseInterface |
|
83
|
|
|
{ |
|
84
|
|
|
try { |
|
85
|
|
|
if ($response = $this->verifyResourceOwner()) { |
|
86
|
|
|
return $response; |
|
87
|
|
|
} |
|
88
|
|
|
|
|
89
|
|
|
$this->authorizationRequest = $this->authorizationRequestBuilder |
|
90
|
|
|
->build($request, $this->authorizationServerEndUser->getAuthenticatedResourceOwner()); |
|
91
|
|
|
} catch (InvalidRequestMethod $e) { |
|
92
|
|
|
return new Response(405); |
|
93
|
|
|
} catch (OAuthException $e) { |
|
94
|
|
|
/** |
|
95
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 |
|
96
|
|
|
* If the request fails due to a missing, invalid, or mismatching |
|
97
|
|
|
* redirection URI, or if the client identifier is missing or invalid, |
|
98
|
|
|
* the authorization server SHOULD inform the resource owner of the |
|
99
|
|
|
* error and MUST NOT automatically redirect the user-agent to the |
|
100
|
|
|
* invalid redirection URI. |
|
101
|
|
|
*/ |
|
102
|
|
|
return new Response(400, ['content-type' => 'application/json'], $e->jsonSerialize()); |
|
103
|
|
|
} catch (InvalidAuthorizationRequest $e) { |
|
104
|
|
|
/** |
|
105
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.2.1 |
|
106
|
|
|
* If the resource owner denies the access request or if the request |
|
107
|
|
|
* fails for reasons other than a missing or invalid redirection URI, |
|
108
|
|
|
* the authorization server informs the client by adding the following |
|
109
|
|
|
* parameters to the query component of the redirection URI using the |
|
110
|
|
|
* "application/x-www-form-urlencoded" format, per Appendix B: |
|
111
|
|
|
* |
|
112
|
|
|
* error |
|
113
|
|
|
* REQUIRED. A single ASCII [USASCII] error code |
|
114
|
|
|
* |
|
115
|
|
|
* error_description |
|
116
|
|
|
* OPTIONAL. Human-readable ASCII [USASCII] text providing |
|
117
|
|
|
* additional information, used to assist the client developer in |
|
118
|
|
|
* understanding the error that occurred. |
|
119
|
|
|
* Values for the "error_description" parameter MUST NOT include |
|
120
|
|
|
* characters outside the set %x20-21 / %x23-5B / %x5D-7E. |
|
121
|
|
|
* |
|
122
|
|
|
* error_uri |
|
123
|
|
|
* OPTIONAL. A URI identifying a human-readable web page with |
|
124
|
|
|
* information about the error, used to provide the client |
|
125
|
|
|
* developer with additional information about the error. |
|
126
|
|
|
* Values for the "error_uri" parameter MUST conform to the |
|
127
|
|
|
* URI-reference syntax and thus MUST NOT include characters |
|
128
|
|
|
* outside the set %x21 / %x23-5B / %x5D-7E. |
|
129
|
|
|
* |
|
130
|
|
|
* state |
|
131
|
|
|
* REQUIRED if a "state" parameter was present in the client |
|
132
|
|
|
* authorization request. The exact value received from the |
|
133
|
|
|
* client. |
|
134
|
|
|
* |
|
135
|
|
|
*/ |
|
136
|
|
|
$oauthException = $e->getOauthException(); |
|
137
|
|
|
$responseData = [ |
|
138
|
|
|
'error' => $oauthException->getError() |
|
139
|
|
|
]; |
|
140
|
|
|
|
|
141
|
|
|
if ($oauthException->getErrorDescription()) { |
|
142
|
|
|
$responseData['error_description'] = $oauthException->getErrorDescription(); |
|
143
|
|
|
} |
|
144
|
|
|
|
|
145
|
|
|
if ($oauthException->getErrorUri()) { |
|
146
|
|
|
$responseData['error_uri'] = $oauthException->getErrorUri(); |
|
147
|
|
|
} |
|
148
|
|
|
|
|
149
|
|
|
if (!empty($e->getState())) { |
|
150
|
|
|
$responseData['state'] = $e->getState(); |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
return $e->getResponseMode()->buildResponse($e->getRedirectUri(), $responseData); |
|
154
|
|
|
} |
|
155
|
|
|
|
|
156
|
|
|
return null; |
|
157
|
|
|
} |
|
158
|
|
|
|
|
159
|
|
|
/** |
|
160
|
|
|
* @param ServerRequestInterface $request |
|
161
|
|
|
* @return ResponseInterface |
|
162
|
|
|
* |
|
163
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
164
|
|
|
* The client constructs the request URI by adding the following |
|
165
|
|
|
* parameters to the query component of the authorization endpoint URI |
|
166
|
|
|
* using the "application/x-www-form-urlencoded" format, per Appendix B: |
|
167
|
|
|
* |
|
168
|
|
|
* response_type |
|
169
|
|
|
* REQUIRED. Value MUST be set to [desired response type]. |
|
170
|
|
|
* |
|
171
|
|
|
* client_id |
|
172
|
|
|
* REQUIRED. The client identifier as described in Section 2.2. |
|
173
|
|
|
* |
|
174
|
|
|
* redirect_uri |
|
175
|
|
|
* OPTIONAL. As described in Section 3.1.2. |
|
176
|
|
|
* |
|
177
|
|
|
* scope |
|
178
|
|
|
* OPTIONAL. The scope of the access request as described by |
|
179
|
|
|
* Section 3.3. |
|
180
|
|
|
* |
|
181
|
|
|
* state |
|
182
|
|
|
* RECOMMENDED. An opaque value used by the client to maintain |
|
183
|
|
|
* state between the request and callback. The authorization |
|
184
|
|
|
* server includes this value when redirecting the user-agent back |
|
185
|
|
|
* to the client. The parameter SHOULD be used for preventing |
|
186
|
|
|
* cross-site request forgery as described in Section 10.12. |
|
187
|
|
|
*/ |
|
188
|
|
|
public function handle(ServerRequestInterface $request): ResponseInterface |
|
189
|
|
|
{ |
|
190
|
|
|
/** |
|
191
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
192
|
|
|
* The authorization server validates the request to ensure that all |
|
193
|
|
|
* required parameters are present and valid. |
|
194
|
|
|
*/ |
|
195
|
|
|
if ($response = $this->verify($request)) { |
|
196
|
|
|
return $response; |
|
197
|
|
|
} |
|
198
|
|
|
|
|
199
|
|
|
if (is_null($this->authorizationRequest)) { |
|
200
|
|
|
throw new \LogicException(); |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
try { |
|
204
|
|
|
/** |
|
205
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
206
|
|
|
* If the request is valid, |
|
207
|
|
|
* the authorization server authenticates the resource owner and obtains |
|
208
|
|
|
* an authorization decision (by asking the resource owner or by |
|
209
|
|
|
* establishing approval via other means). |
|
210
|
|
|
*/ |
|
211
|
|
|
if ($response = $this->verifyConsent($this->authorizationRequest)) { |
|
212
|
|
|
return $response; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
$responseData = $this->authorizationRequest->getResponseType() |
|
216
|
|
|
->handleAuthorizationRequest($this->authorizationRequest); |
|
217
|
|
|
} catch (OAuthException $e) { |
|
218
|
|
|
/** |
|
219
|
|
|
* If the Authorization Server encounters any error, it MUST return an error response, per Section 3.1.2.6. |
|
220
|
|
|
*/ |
|
221
|
|
|
$responseData = [ |
|
222
|
|
|
'error' => $e->getError() |
|
223
|
|
|
]; |
|
224
|
|
|
if ($e->getErrorDescription()) { |
|
225
|
|
|
$responseData['error_description'] = $e->getErrorDescription(); |
|
226
|
|
|
} |
|
227
|
|
|
if ($e->getErrorUri()) { |
|
228
|
|
|
$responseData['error_uri'] = $e->getErrorUri(); |
|
229
|
|
|
} |
|
230
|
|
|
} |
|
231
|
|
|
|
|
232
|
|
|
// todo move state to flows |
|
233
|
|
|
if (!empty($this->authorizationRequest->getState())) { |
|
234
|
|
|
$responseData['state'] = $this->authorizationRequest->getState(); |
|
235
|
|
|
} |
|
236
|
|
|
|
|
237
|
|
|
/** |
|
238
|
|
|
* @see https://tools.ietf.org/html/rfc6749#section-4.1.1 |
|
239
|
|
|
* When a decision is established, the authorization server directs the |
|
240
|
|
|
* user-agent to the provided client redirection URI using an HTTP |
|
241
|
|
|
* redirection response, or by other means available to it via the |
|
242
|
|
|
* user-agent. |
|
243
|
|
|
*/ |
|
244
|
|
|
return $this->authorizationRequest->getResponseMode() |
|
245
|
|
|
->buildResponse($this->authorizationRequest->getRedirectUri(), $responseData); |
|
246
|
|
|
} |
|
247
|
|
|
|
|
248
|
|
|
|
|
249
|
|
|
/** |
|
250
|
|
|
* @param AuthorizationRequestInterface $authorizationRequest |
|
251
|
|
|
* @return null|ResponseInterface |
|
252
|
|
|
* @throws OAuthException |
|
253
|
|
|
*/ |
|
254
|
|
|
protected function verifyConsent(AuthorizationRequestInterface $authorizationRequest): ?ResponseInterface |
|
255
|
|
|
{ |
|
256
|
|
|
$consentGiven = $this->authorizationServerEndUser->hasGivenConsent($authorizationRequest->getClient(), $authorizationRequest->getScopes()); |
|
257
|
|
|
if (is_null($consentGiven)) { |
|
258
|
|
|
return $this->authorizationServerEndUser->obtainConsent($authorizationRequest); |
|
259
|
|
|
} |
|
260
|
|
|
|
|
261
|
|
|
if (empty($consentGiven)) { |
|
262
|
|
|
throw new OAuthException('access_denied', 'The resource owner denied the request.', |
|
263
|
|
|
'https://tools.ietf.org/html/rfc6749#section-4.1'); |
|
264
|
|
|
} |
|
265
|
|
|
|
|
266
|
|
|
return null; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
protected function verifyResourceOwner(): ?ResponseInterface |
|
270
|
|
|
{ |
|
271
|
|
|
if (!$this->authorizationServerEndUser->getAuthenticatedResourceOwner()) { |
|
272
|
|
|
return $this->authorizationServerEndUser->authenticateResourceOwner(); |
|
273
|
|
|
} |
|
274
|
|
|
return null; |
|
275
|
|
|
} |
|
276
|
|
|
|
|
277
|
|
|
|
|
278
|
|
|
/** |
|
279
|
|
|
* @return AuthorizationRequest|null |
|
280
|
|
|
*/ |
|
281
|
|
|
public function getAuthorizationRequest(): ?AuthorizationRequest |
|
282
|
|
|
{ |
|
283
|
|
|
return $this->authorizationRequest; |
|
284
|
|
|
} |
|
285
|
|
|
|
|
286
|
|
|
/** |
|
287
|
|
|
* @return EndUserInterface |
|
288
|
|
|
*/ |
|
289
|
|
|
public function getAuthorizationServerEndUser(): EndUserInterface |
|
290
|
|
|
{ |
|
291
|
|
|
return $this->authorizationServerEndUser; |
|
292
|
|
|
} |
|
293
|
|
|
} |