/
REST.php
215 lines (198 loc) · 6.2 KB
/
REST.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
<?php
namespace FOO;
/**
* Class StatusException
* Base class for HTTP status exceptions.
* @package FOO
*/
abstract class StatusException extends \Exception {
/** @var string Status string. */
public static $MSG = 'Unknown';
/**
* Constructor.
*/
public function __construct($msg=null) {
$this->message = is_null($msg) ? static::$MSG:$msg;
}
abstract public function getStatus();
}
/**
* Class NotFoundException
* HTTP 404 Error
* @package FOO
*/
class NotFoundException extends StatusException {
public static $MSG = 'Not found';
public function getStatus() { return 404; }
}
/**
* Class UnauthorizedException
* HTTP 401 Error
* @package FOO
*/
class UnauthorizedException extends StatusException {
public static $MSG = 'Unauthorized';
public function getStatus() { return 401; }
}
/**
* Class ForbiddenException
* HTTP 403 Error
* @package FOO
*/
class ForbiddenException extends StatusException {
public static $MSG = 'Forbidden';
public function getStatus() { return 403; }
}
/**
* Class InternalErrorException
* HTTP 500 Error
* @package FOO
*/
class InternalErrorException extends StatusException {
public static $MSG = 'Server error';
public function getStatus() { return 500; }
}
/**
* Class NotImplementedException
* HTTP 501 Error
* @package FOO
*/
class NotImplementedException extends StatusException {
public static $MSG = 'Not implemented';
public function getStatus() { return 501; }
}
/**
* Class REST
* REST router.
* @package FOO
*/
abstract class REST {
/**
* Route a request to the appropriate method. Handles different input types and exceptions. Outputs JSON to the
* client.
*/
public function route() {
$ret = self::format();
try {
$this->checkAuthorization();
// Attempt to read input as JSON.
$json = file_get_contents('php://input');
$data = json_decode($json, true);
if(is_null($data)) {
$data = $_POST;
}
// Check the nonce if this is not a GET request.
if($_SERVER['REQUEST_METHOD'] != 'GET' && Auth::isWeb()) {
$nnc1 = Util::get($_SERVER, 'HTTP_X_NONCE');
$nnc2 = Util::get($data, '_nonce');
if(strlen($nnc1) == 0 && strlen($nnc2) == 0) {
throw new UnauthorizedException('Empty nonce specified');
}
if(!Nonce::check($nnc1) && !Nonce::check($nnc2)) {
throw new UnauthorizedException('Invalid nonce specified');
}
}
// Now try running the appropriate method.
switch($_SERVER['REQUEST_METHOD']) {
case 'GET': $ret = $this->GET($_GET); break;
case 'POST': $ret = $this->POST($_GET, $data); break;
case 'PUT': $ret = $this->PUT($_GET, $data); break;
case 'DELETE': $ret = $this->DELETE($_GET, $data); break;
default: throw new NotImplementedException('Method not supported');
}
} catch(StatusException $e) {
http_response_code($e->getStatus());
$ret['success'] = false;
$ret['message'] = $e->getMessage();
Logger::except($e);
} catch(ValidationException $e) {
http_response_code(500);
$ret['success'] = false;
$ret['message'] = $e->getMessage();
Logger::except($e);
} catch(\Exception $e) {
http_response_code(500);
$ret['success'] = false;
$ret['message'] = $e->getMessage();
Logger::except($e);
}
header('Content-Type: application/json charset=utf-8');
print json_encode($ret);
exit();
}
/**
* Verifies that the current user is allowed to execute this request.
* @throws StatusException
*/
public function checkAuthorization() {
if(!Auth::isAuthenticated()) {
throw new UnauthorizedException('Authentication required');
}
}
/**
* Format data for output.
* @param array $data The output data.
* @param bool $success Whether the request was successful.
* @param string[]|string $message The message.
* @return array Formatted data.
*/
public function format($data=null, $success=true, $message='') {
// Return the existing object if it's already formatted.
if(
is_array($data) &&
Util::exists($data, 'data') && Util::exists($data, 'success') &&
Util::exists($data, 'message') && Util::exists($data, 'authenticated')
) {
return $data;
}
return [
'data' => $data,
'success' => $success,
'message' => $message,
'authenticated' => Auth::isAuthenticated()
];
}
/**
* GET request.
* @param array $get The url parameters.
* @return array Data.
* @throws NotImplementedException
* @suppress PhanTypeMissingReturn
*/
public function GET(array $get) {
throw new NotImplementedException('Method not supported');
}
/**
* POST request.
* @param array $get The url parameters.
* @param array $data The body parameters.
* @return array Data.
* @throws NotImplementedException
* @suppress PhanTypeMissingReturn
*/
public function POST(array $get, array $data) {
throw new NotImplementedException('Method not supported');
}
/**
* PUT request.
* @param array $get The url parameters.
* @param array $data The body parameters.
* @return array Data.
* @throws NotImplementedException
* @suppress PhanTypeMissingReturn
*/
public function PUT(array $get, array $data) {
throw new NotImplementedException('Method not supported');
}
/**
* DELETE request.
* @param array $get The url parameters.
* @param array $data The body parameters.
* @return array Data.
* @throws NotImplementedException
* @suppress PhanTypeMissingReturn
*/
public function DELETE(array $get, array $data) {
throw new NotImplementedException('Method not supported');
}
};