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
|
|
|
} |