Repository navigation
Expand file tree
/
Copy pathrest.php
More file actions
175 lines (154 loc) · 5.72 KB
/
Copy pathrest.php
File metadata and controls
175 lines (154 loc) · 5.72 KB
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
<?php
// API requests must never read or write a FusionPBX browser session, so
// anything written to $_SESSION here only lives for this request.
// close a session started by session.auto_start without saving it: keep a
// browser user's session as it was, discard a new empty one and its cookie
if(session_status() === PHP_SESSION_ACTIVE) {
if(empty($_SESSION)) {
session_destroy();
} else {
session_abort();
}
header_remove('Set-Cookie');
}
$_SESSION = array();
// FusionPBX's require.php switches domains when the query string asks for it.
// that checks a permission before we know the key's user, and FusionPBX keeps
// the (empty) result for the whole request. the API only reads the JSON body
$_GET = array();
// ask require.php not to start a session. in case it starts one anyway, make
// sure it can't resume a browser session from its cookie or send a cookie
$no_session = true;
ini_set('session.use_cookies', '0');
ini_set('session.use_only_cookies', '1');
ini_set('session.use_trans_sid', '0');
require_once "root.php";
require_once "resources/require.php";
require_once "lib/input_validation.php";
require_once "lib/auth.php";
require_once "lib/fields.php";
require_once "lib/fs_parser.php";
require_once "lib/call_center.php";
require_once "lib/recordings.php";
require_once "lib/calls.php";
// whatever require.php did, never save the session. cookies are disabled, so
// any session started from here on is a new, empty one and safe to destroy
if(session_status() === PHP_SESSION_ACTIVE) {
session_destroy();
}
register_shutdown_function(function() {
if(session_status() === PHP_SESSION_ACTIVE) {
session_destroy();
}
});
function return_error($msg, $code=500) {
http_response_code($code);
echo json_encode(array("error" => $msg));
die();
}
if(!isset($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'])) {
error_log("rejecting request with no auth");
return_error("unauthorized", 401);
}
if(!is_uuid($_SERVER['PHP_AUTH_USER'])) {
error_log("rejecting request with malformed token identifier");
return_error("unauthorized", 401);
}
// the key must be enabled, not expired, and bound to an enabled user of an enabled domain
$key = rest_api_find_key(database::new(), $_SERVER['PHP_AUTH_USER']);
$rejection = rest_api_key_rejection($key, $_SERVER['PHP_AUTH_PW'], time());
if($rejection !== null) {
error_log("rejecting request: ".$rejection." (".$_SERVER['PHP_AUTH_USER'].")");
return_error("unauthorized", 401);
}
// from here on the request runs as the key's user
rest_api_start_user_request($key);
// set the key last used time
$sql = "UPDATE rest_api_keys SET last_used = NOW() WHERE key_uuid = :key_id";
database::new()->execute($sql, array('key_id' => $_SERVER['PHP_AUTH_USER']));
$body = json_decode(file_get_contents('php://input'));
if(!is_object($body)) {
return_error("request body must be a JSON object", 400);
}
$validation_errors = ensure_parameters($body, array("action"));
if($validation_errors) {
return_error($validation_errors, 400);
}
// action and app names end up in include paths, so only allow plain names
$action = is_string($body->action) ? strtolower($body->action) : "";
if(!preg_match('/^[a-z0-9-]+$/D', $action)) {
return_error("unknown action", 400);
}
$file = __DIR__."/actions/".$action.".php";
if(!empty($body->app)) {
$app = $body->app;
if(!is_string($app) || !preg_match('/^[a-z0-9_]+$/D', $app)) {
return_error("unknown app", 400);
}
$app_dir = realpath(__DIR__."/../".$app);
$app_index = $app_dir."/app_api.php";
if(!$app_dir || !file_exists($app_index)) {
return_error("unknown app", 400);
}
include($app_index);
if(!empty($app_api[$app][$action])) {
// the app's own mapping must not point outside the app directory
$file = realpath($app_dir."/".$app_api[$app][$action]);
if(!$file || strpos($file, $app_dir."/") !== 0) {
return_error("unknown action", 400);
}
}
}
if(!file_exists($file)) {
return_error("unknown action", 400);
}
// only the action file can declare what it needs: anything set before, e.g.
// by an app's app_api.php, doesn't count. undeclared actions never run
$required_permissions = null;
include($file);
if(!is_array($required_permissions)) {
error_log("refusing to run action ".$action.": it does not declare \$required_permissions");
return_error("action does not declare permissions", 500);
}
$context = rest_api_resolve_domain($body, $key);
if(isset($context['error'])) {
return_error($context['error'], $context['code']);
}
// checked before the parameters, so callers without access don't learn what an action expects
$missing_permissions = rest_api_missing_permissions($required_permissions);
if($missing_permissions) {
http_response_code(403);
echo json_encode(array("error" => "forbidden", "missing_permissions" => $missing_permissions));
die();
}
$validation_errors = ensure_parameters($body, $required_params);
if($validation_errors) {
return_error($validation_errors, 400);
}
if(function_exists('do_action')) {
// actions declared as do_action($body) just ignore the context
$resp = do_action($body, $context);
if(!empty($resp['code'])) {
http_response_code($resp['code']);
unset($resp['code']);
} elseif(!empty($resp['error'])) {
http_response_code(500);
}
// an action can answer with a file, streamed rather than held in memory
if(isset($resp['send_file']) && is_array($resp['send_file'])) {
$file = $resp['send_file'];
header('Content-Type: '.$file['content_type']);
header('Content-Length: '.filesize($file['path']));
// the name can't break out of the header's quotes
header('Content-Disposition: attachment; filename="'.preg_replace('/[^A-Za-z0-9._-]/', '_', $file['name']).'"');
while(ob_get_level() > 0) {
ob_end_clean();
}
readfile($file['path']);
exit;
}
// a 204 has no body
if(http_response_code() !== 204) {
echo json_encode($resp);
}
}