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