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