-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathGOAuth2AuthorizationServer.php
309 lines (257 loc) · 10.3 KB
/
GOAuth2AuthorizationServer.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
<?php
require_once 'GOAuth2.php';
/**
* OAuth2.0-compliant authorization endpoint.
* @package GOAuth2
*/
abstract class GOAuth2AuthorizationServer {
// An array of URIs, indexed by error type, that may be provided to the client.
private $error_uris = array();
// Whether SSL is enforced on this authorization server.
private $enforce_ssl;
/**
* Class Constructor
*
* @param Bool $enforce_ssl Whether to enforce SSL connections (highly
* recommended)
*
* @return AuthorizationServer
*/
public function __construct($enforce_ssl = true) {
$this->enforce_ssl = $enforce_ssl;
}
/**
* Process an authorization request. This checks that all required parameters
* are set, and that a client and redirect URI were provided. In compliance
* with the OAuth2.0 spec, an invalid client or invalid redirect URI will *not*
* result in a redirect back to the client application, but will throw exceptions
* that should be caught by the calling script.
*
* Any other error - for example, a missing or invalid response type, or an invalid
* or unknown scope specification - will redirect the user-agent back to the client
* with an error message.
*
* If both $user and $user_decision are set, the function will redirect the user-agent
* back to the client, with either the granted code or the approprite error message.
*
* @param array $get The $_GET variables from the authorization request.
* @param mixed $user A means for uniquely identifying the end-user being
* served the request. This would most commonly be, for
* example, a 'user_id' stored in the $_SESSION variable.
* This value is passed on to the function which generates
* a new authorization code.
* @param bool $user_decision Either null, true or false, representing the end-user's
* decision on whether to grant the request or not.
*
* @throws GOAuth2InvalidClientException
* @throws GOAuth2InvalidRedirectURIException
*/
public function processAuthorizationRequest($get, $user = null, $user_decision = null) {
// Check SSL
if($this->enforce_ssl) {
if($_SERVER['HTTPS'] !== 'on') {
throw new GOAuth2SSLException('Attempted to connect to GOAuth2 authorization server over non-HTTPS channel.');
}
}
// Extract parameters from the GET request
$params = array('response_type', 'client_id', 'redirect_uri', 'scope', 'state');
foreach($params as $param) {
$$param = isset($get[$param]) ? $get[$param] : null;
}
// Validate the client ID
// Throw an exception rather than send a redirect, as per the OAuth2.0 spec's
// requirement that authorization server's don't redirect in this case.
if(!$this->validateClient($client_id)) {
throw new GOAuth2InvalidClientException(GOAuth2::getErrorDescription(GOAuth2::ERROR_INVALID_CLIENT));
}
// Validate the Redirect URI
// Throw an exception rather than send a redirect, as per the OAuth2.0 spec's
// requirement that authorization server's don't redirect in this case.
if(!($redirect_uri = $this->validateRedirectURI($client_id, $redirect_uri))) {
throw new GOAuth2InvalidRedirectURIException(GOAuth2::getErrorDescription(GOAuth2::ERROR_INVALID_REDIRECT_URI));
}
// Client ID and Redireect URI validated, check that a response type was provided
if(!$response_type) {
$this->sendErrorRedirect($redirect_uri, GOAuth2::ERROR_INVALID_REQUEST, $state);
}
// Check that the response type is one we support (currently only 'code')
if($response_type !== GOAuth2::RESPONSE_TYPE_CODE) {
$this->sendErrorRedirect($redirect_uri, GOAuth2::ERROR_UNSUPPORTED_RESPONSE_TYPE, $state);
}
// Check that the scope requested is valid for users of the specified client
if(!$this->validateScope($client_id, $scope)) {
$this->sendErrorRedirect($redirect_uri, GOAuth2::ERROR_INVALID_SCOPE);
}
// If this request represents an end-user decision on whether to grant/deny
// access, process it.
if($user_decision !== null) {
// Check to see if the user denied the request.
if(!$user_decision) {
$this->sendErrorRedirect($redirect_uri, GOAuth2::ERROR_ACCESS_DENIED);
}
// The request was approved by the end user, create a new authentication token and return to the client.
$code = $this->generateNewAuthorizationCode($client_id, $redirect_uri, $user, $scope);
$this->sendCodeRedirect($code, $state);
}
}
/**
* Check that the redirect URI provided in the request is valid.
*
* @param String $client_id The ID of the client making the request.
* @param String $redirect_uri The redirect URI provided in the request, or
* null if none was provided.
* @return String The redirect URI that should be used (either
* that specified or that registered as appropriate).
* Null will be returned if no URI could be validated.
*/
private function validateRedirectURI($client_id, $redirect_uri) {
// Check the client exists.
if(!$this->validateClient($client_id)) {
return null;
}
// Check that the provided redirect URI is valid.
$registered_redirect_uri = $this->getRegisteredRedirectURIForClient($client_id);
// If there's no registered URI, the provided URI is valid as long as it was provided.
if(!$registered_redirect_uri) {
return $redirect_uri;
}
// Otherwise, check that the provided URI matches the registered URI.
if($registered_redirect_uri === rawurldecode($redirect_uri)) {
return $registered_redirect_uri;
}
// Couldn't validate, return null.
return null;
}
/**
* Redirect back to the client with an authorization code.
*
* @param String $redirect_uri The URI to redirect back to.
* @param String $code The authorization code to return.
* @param String $state Optional. The state parameter if
* specified in the original request.
*/
private function sendCodeRedirect(GOAuth2AuthorizationCode $code, $state = null) {
$params = array(
'code' => $code->code
);
// Append the state if defined.
if($state) {
$params['state'] = $state;
}
$this->sendRedirect($code->redirect_uri, $params);
}
/**
* Send a redirect to the specified $redirect_uri with error information.
*
* @param String $redirect_uri The redirect URI.
* @param String $error The error to send.
* @param String $state Optional. The 'state' parameter if passed
* in the initial authorization request.
*/
private function sendErrorRedirect($redirect_uri, $error, $state = null) {
$params = array(
'error' => $error,
'error_description' => GOAuth2::getErrorDescription($error)
);
// Append the error URI if defined
if(isset($this->error_uris[$error])) {
$params['error_uri'] = $this->error_uris[$error];
}
// Append the state if defined
if($state) {
$params['state'] = $state;
}
$this->sendRedirect($redirect_uri, $params);
}
/**
* Redirect the current request to the given URI.
*
* @param String $uri The URI to redirect to.
* @param array $params Optional. An array of parameters to add to the
* querystring of the redirect URI.
*/
private function sendRedirect($redirect_uri, $params = array()) {
// Extract any existing parameters from the redirect URI.
$uri_parts = parse_url($redirect_uri);
parse_str($uri_parts['query'], $existing_params);
$params = array_merge($params, $existing_params);
// Rebuild the URI with all query parameters
$params = http_build_query($params);
$uri_parts = array(
$uri_parts['scheme'] ? $uri_parts['scheme'] . '://' : '',
$uri_parts['host'],
$uri_parts['path'],
empty($params) ? '' : "?$params"
);
$redirect_uri = implode('', $uri_parts);
// Clean the output buffer to eliminate any whitespace.
@ob_end_clean();
// Set the response status code
header(GOAuth2::HTTP_302);
// Set the URI
header("Location: $redirect_uri");
// Cease processing
exit;
}
/**
* FUNCTIONS THAT REQUIRE REIMPLEMENTATION
*
* The following functions MUST be implemented in any inheriting subclass.
*/
/**
* Check whether the specified client exists.
*
* @param String $client_id
* @return Bool True if client exists, False otherwise.
*/
protected function validateClient($client_id) {
throw new Exception('GOAuth2AuthorizationServer::validateClient() not implemented.');
}
/**
* Generate a new Authorization Code for the specified client, redirect uri
* and scope.
*
* @param string $client_id The ID of the client the authorization code is
* being generated for.
* @param string $redirect_uri The redirect URI the authorization code is being
* sent to.
* @param mixed $user A means for uniquely identifying the end-user to
* the server implementation (eg a user ID).
* @param string $scope The scope the authorization code should apply to.
*
* @return GOAuth2AuthorizationCode
*/
protected function generateNewAuthorizationCode($client_id, $redirect_uri, $user, $scope) {
throw new Exception('GOAuth2AuthorizationServer::generateNewAuthorizationCode() not implemented.');
}
/**
* FUNCTIONS THAT REQUIRE REIMPLEMENTATION IN SOME SCENARIOS
*
* The following functions MAY need to be reimplemented in an inheriting subclass.
*/
/**
* Get any registered redirect URI for the specified client.
*
* This function MUST be reimplemented if your authorization server allows clients
* to preregister their redirect URIs, which is the recommended behaviour.
*
* @param String $client_id
* @return String $registered_redirect_uri URI in decoded format if found, null otherwise.
*/
protected function getRegisteredRedirectURIForClient($client_id) {
return null;
}
/**
* Validate that the specified scope is validate for the given client.
*
* This function MUST be reimplemented if you want to check the requested scope.
*
* @param string $client_id The ID of the client making the request.
* @param string $scope The space-delimited list of requested scopes given in
* the authorization request.
* @return Bool True if requested scope is OK, false otherwise.
*/
protected function validateScope($client_id, $scope) {
return true;
}
}