This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | namespace Elgg; |
||
3 | |||
4 | /** |
||
5 | * WARNING: API IN FLUX. DO NOT USE DIRECTLY. |
||
6 | * |
||
7 | * Use the elgg_* versions instead. |
||
8 | * |
||
9 | * @access private |
||
10 | * |
||
11 | * @package Elgg.Core |
||
12 | * @subpackage Actions |
||
13 | * @since 1.9.0 |
||
14 | */ |
||
15 | class ActionsService { |
||
16 | |||
17 | /** |
||
18 | * Registered actions storage |
||
19 | * @var array |
||
20 | */ |
||
21 | private $actions = array(); |
||
22 | |||
23 | /** |
||
24 | * The current action being processed |
||
25 | * @var string |
||
26 | */ |
||
27 | private $currentAction = null; |
||
28 | |||
29 | /** |
||
30 | * @see action |
||
31 | * @access private |
||
32 | */ |
||
33 | public function execute($action, $forwarder = "") { |
||
34 | $action = rtrim($action, '/'); |
||
35 | $this->currentAction = $action; |
||
36 | |||
37 | // @todo REMOVE THESE ONCE #1509 IS IN PLACE. |
||
38 | // Allow users to disable plugins without a token in order to |
||
39 | // remove plugins that are incompatible. |
||
40 | // Login and logout are for convenience. |
||
41 | // file/download (see #2010) |
||
42 | $exceptions = array( |
||
43 | 'admin/plugins/disable', |
||
44 | 'logout', |
||
45 | 'file/download', |
||
46 | ); |
||
47 | |||
48 | if (!in_array($action, $exceptions)) { |
||
49 | // All actions require a token. |
||
50 | $this->gatekeeper($action); |
||
51 | } |
||
52 | |||
53 | $forwarder = str_replace(_elgg_services()->config->getSiteUrl(), "", $forwarder); |
||
54 | $forwarder = str_replace("http://", "", $forwarder); |
||
55 | $forwarder = str_replace("@", "", $forwarder); |
||
56 | if (substr($forwarder, 0, 1) == "/") { |
||
57 | $forwarder = substr($forwarder, 1); |
||
58 | } |
||
59 | |||
60 | if (!isset($this->actions[$action])) { |
||
61 | register_error(_elgg_services()->translator->translate('actionundefined', array($action))); |
||
62 | } elseif (!_elgg_services()->session->isAdminLoggedIn() && ($this->actions[$action]['access'] === 'admin')) { |
||
63 | register_error(_elgg_services()->translator->translate('actionunauthorized')); |
||
64 | } elseif (!_elgg_services()->session->isLoggedIn() && ($this->actions[$action]['access'] !== 'public')) { |
||
65 | register_error(_elgg_services()->translator->translate('actionloggedout')); |
||
66 | } else { |
||
67 | // To quietly cancel the action file, return a falsey value in the "action" hook. |
||
68 | if (_elgg_services()->hooks->trigger('action', $action, null, true)) { |
||
69 | if (is_file($this->actions[$action]['file']) && is_readable($this->actions[$action]['file'])) { |
||
70 | self::includeFile($this->actions[$action]['file']); |
||
71 | } else { |
||
72 | register_error(_elgg_services()->translator->translate('actionnotfound', array($action))); |
||
73 | } |
||
74 | } |
||
75 | } |
||
76 | |||
77 | $forwarder = empty($forwarder) ? REFERER : $forwarder; |
||
78 | forward($forwarder); |
||
79 | } |
||
80 | |||
81 | /** |
||
82 | * Include an action file with isolated scope |
||
83 | * |
||
84 | * @param string $file File to be interpreted by PHP |
||
85 | * @return void |
||
86 | */ |
||
87 | protected static function includeFile($file) { |
||
88 | include $file; |
||
89 | } |
||
90 | |||
91 | /** |
||
92 | * @see elgg_register_action |
||
93 | * @access private |
||
94 | */ |
||
95 | 2 | public function register($action, $filename = "", $access = 'logged_in') { |
|
96 | // plugins are encouraged to call actions with a trailing / to prevent 301 |
||
97 | // redirects but we store the actions without it |
||
98 | 2 | $action = rtrim($action, '/'); |
|
99 | |||
100 | 2 | if (empty($filename)) { |
|
101 | |||
102 | $path = _elgg_services()->config->get('path'); |
||
103 | if ($path === null) { |
||
104 | $path = ""; |
||
105 | } |
||
106 | |||
107 | $filename = $path . "actions/" . $action . ".php"; |
||
108 | } |
||
109 | |||
110 | 2 | $this->actions[$action] = array( |
|
111 | 2 | 'file' => $filename, |
|
112 | 2 | 'access' => $access, |
|
113 | ); |
||
114 | 2 | return true; |
|
115 | } |
||
116 | |||
117 | /** |
||
118 | * @see elgg_unregister_action |
||
119 | * @access private |
||
120 | */ |
||
121 | 1 | public function unregister($action) { |
|
122 | 1 | if (isset($this->actions[$action])) { |
|
123 | 1 | unset($this->actions[$action]); |
|
124 | 1 | return true; |
|
125 | } else { |
||
126 | 1 | return false; |
|
127 | } |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * @see validate_action_token |
||
132 | * @access private |
||
133 | */ |
||
134 | public function validateActionToken($visible_errors = true, $token = null, $ts = null) { |
||
135 | if (!$token) { |
||
136 | $token = get_input('__elgg_token'); |
||
137 | } |
||
138 | |||
139 | if (!$ts) { |
||
140 | $ts = get_input('__elgg_ts'); |
||
141 | } |
||
142 | |||
143 | $session_id = _elgg_services()->session->getId(); |
||
144 | |||
145 | if (($token) && ($ts) && ($session_id)) { |
||
146 | if ($this->validateTokenOwnership($token, $ts)) { |
||
147 | if ($this->validateTokenTimestamp($ts)) { |
||
148 | // We have already got this far, so unless anything |
||
149 | // else says something to the contrary we assume we're ok |
||
150 | $returnval = _elgg_services()->hooks->trigger('action_gatekeeper:permissions:check', 'all', array( |
||
151 | 'token' => $token, |
||
152 | 'time' => $ts |
||
153 | ), true); |
||
154 | |||
155 | if ($returnval) { |
||
156 | return true; |
||
157 | } else if ($visible_errors) { |
||
158 | register_error(_elgg_services()->translator->translate('actiongatekeeper:pluginprevents')); |
||
159 | } |
||
160 | View Code Duplication | } else if ($visible_errors) { |
|
161 | // this is necessary because of #5133 |
||
162 | if (elgg_is_xhr()) { |
||
163 | register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl()))); |
||
164 | } else { |
||
165 | register_error(_elgg_services()->translator->translate('actiongatekeeper:timeerror')); |
||
166 | } |
||
167 | } |
||
168 | View Code Duplication | } else if ($visible_errors) { |
|
169 | // this is necessary because of #5133 |
||
170 | if (elgg_is_xhr()) { |
||
171 | register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl()))); |
||
172 | } else { |
||
173 | register_error(_elgg_services()->translator->translate('actiongatekeeper:tokeninvalid')); |
||
174 | } |
||
175 | } |
||
176 | } else { |
||
177 | $req = _elgg_services()->request; |
||
178 | $length = $req->server->get('CONTENT_LENGTH'); |
||
179 | $post_count = count($req->request); |
||
180 | if ($length && $post_count < 1) { |
||
181 | // The size of $_POST or uploaded file has exceed the size limit |
||
182 | $error_msg = _elgg_services()->hooks->trigger('action_gatekeeper:upload_exceeded_msg', 'all', array( |
||
183 | 'post_size' => $length, |
||
184 | 'visible_errors' => $visible_errors, |
||
185 | ), _elgg_services()->translator->translate('actiongatekeeper:uploadexceeded')); |
||
186 | } else { |
||
187 | $error_msg = _elgg_services()->translator->translate('actiongatekeeper:missingfields'); |
||
188 | } |
||
189 | if ($visible_errors) { |
||
190 | register_error($error_msg); |
||
191 | } |
||
192 | } |
||
193 | |||
194 | return false; |
||
195 | } |
||
196 | |||
197 | /** |
||
198 | * Is the token timestamp within acceptable range? |
||
199 | * |
||
200 | * @param int $ts timestamp from the CSRF token |
||
201 | * |
||
202 | * @return bool |
||
203 | */ |
||
204 | protected function validateTokenTimestamp($ts) { |
||
205 | $timeout = $this->getActionTokenTimeout(); |
||
206 | $now = time(); |
||
207 | return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout)); |
||
208 | } |
||
209 | |||
210 | /** |
||
211 | * @see \Elgg\ActionsService::validateActionToken |
||
212 | * @access private |
||
213 | * @since 1.9.0 |
||
214 | * @return int number of seconds that action token is valid |
||
215 | */ |
||
216 | public function getActionTokenTimeout() { |
||
217 | if (($timeout = _elgg_services()->config->get('action_token_timeout')) === null) { |
||
218 | // default to 2 hours |
||
219 | $timeout = 2; |
||
220 | } |
||
221 | $hour = 60 * 60; |
||
222 | return (int)((float)$timeout * $hour); |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * @see action_gatekeeper |
||
227 | * @access private |
||
228 | */ |
||
229 | public function gatekeeper($action) { |
||
230 | if ($action === 'login') { |
||
231 | if ($this->validateActionToken(false)) { |
||
232 | return true; |
||
233 | } |
||
234 | |||
235 | $token = get_input('__elgg_token'); |
||
236 | $ts = (int)get_input('__elgg_ts'); |
||
237 | if ($token && $this->validateTokenTimestamp($ts)) { |
||
238 | // The tokens are present and the time looks valid: this is probably a mismatch due to the |
||
239 | // login form being on a different domain. |
||
240 | register_error(_elgg_services()->translator->translate('actiongatekeeper:crosssitelogin')); |
||
241 | |||
242 | forward('login', 'csrf'); |
||
243 | } |
||
244 | |||
245 | // let the validator send an appropriate msg |
||
246 | $this->validateActionToken(); |
||
247 | |||
248 | } else if ($this->validateActionToken()) { |
||
249 | return true; |
||
250 | } |
||
251 | |||
252 | forward(REFERER, 'csrf'); |
||
253 | } |
||
254 | |||
255 | /** |
||
256 | * Was the given token generated for the session defined by session_token? |
||
257 | * |
||
258 | * @param string $token CSRF token |
||
259 | * @param int $timestamp Unix time |
||
260 | * @param string $session_token Session-specific token |
||
261 | * |
||
262 | * @return bool |
||
263 | * @access private |
||
264 | */ |
||
265 | public function validateTokenOwnership($token, $timestamp, $session_token = '') { |
||
266 | $required_token = $this->generateActionToken($timestamp, $session_token); |
||
267 | |||
268 | return _elgg_services()->crypto->areEqual($token, $required_token); |
||
0 ignored issues
–
show
|
|||
269 | } |
||
270 | |||
271 | /** |
||
272 | * Generate a token from a session token (specifying the user), the timestamp, and the site key. |
||
273 | * |
||
274 | * @see generate_action_token |
||
275 | * |
||
276 | * @param int $timestamp Unix timestamp |
||
277 | * @param string $session_token Session-specific token |
||
278 | * |
||
279 | * @return string |
||
280 | * @access private |
||
281 | */ |
||
282 | public function generateActionToken($timestamp, $session_token = '') { |
||
283 | if (!$session_token) { |
||
284 | $session_token = elgg_get_session()->get('__elgg_session'); |
||
285 | if (!$session_token) { |
||
286 | return false; |
||
287 | } |
||
288 | } |
||
289 | |||
290 | return _elgg_services()->crypto->getHmac([(int)$timestamp, $session_token], 'md5') |
||
291 | ->getToken(); |
||
292 | } |
||
293 | |||
294 | /** |
||
295 | * @see elgg_action_exists |
||
296 | * @access private |
||
297 | */ |
||
298 | 3 | public function exists($action) { |
|
299 | 3 | return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file'])); |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * @see ajax_forward_hook |
||
304 | * @access private |
||
305 | */ |
||
306 | public function ajaxForwardHook($hook, $reason, $return, $params) { |
||
307 | if (elgg_is_xhr()) { |
||
308 | // always pass the full structure to avoid boilerplate JS code. |
||
309 | $params = array_merge($params, array( |
||
310 | 'output' => '', |
||
311 | 'status' => 0, |
||
312 | 'system_messages' => array( |
||
313 | 'error' => array(), |
||
314 | 'success' => array() |
||
315 | ) |
||
316 | )); |
||
317 | |||
318 | //grab any data echo'd in the action |
||
319 | $output = ob_get_clean(); |
||
320 | |||
321 | //Avoid double-encoding in case data is json |
||
322 | $json = json_decode($output); |
||
323 | if (isset($json)) { |
||
324 | $params['output'] = $json; |
||
325 | } else { |
||
326 | $params['output'] = $output; |
||
327 | } |
||
328 | |||
329 | //Grab any system messages so we can inject them via ajax too |
||
330 | $system_messages = _elgg_services()->systemMessages->dumpRegister(); |
||
331 | |||
332 | if (isset($system_messages['success'])) { |
||
333 | $params['system_messages']['success'] = $system_messages['success']; |
||
334 | } |
||
335 | |||
336 | if (isset($system_messages['error'])) { |
||
337 | $params['system_messages']['error'] = $system_messages['error']; |
||
338 | $params['status'] = -1; |
||
339 | } |
||
340 | |||
341 | if ($reason == 'walled_garden') { |
||
342 | $reason = '403'; |
||
343 | } |
||
344 | $httpCodes = array( |
||
345 | '400' => 'Bad Request', |
||
346 | '401' => 'Unauthorized', |
||
347 | '403' => 'Forbidden', |
||
348 | '404' => 'Not Found', |
||
349 | '407' => 'Proxy Authentication Required', |
||
350 | '500' => 'Internal Server Error', |
||
351 | '503' => 'Service Unavailable', |
||
352 | ); |
||
353 | |||
354 | if (isset($httpCodes[$reason])) { |
||
355 | header("HTTP/1.1 $reason {$httpCodes[$reason]}", true); |
||
356 | } |
||
357 | |||
358 | $context = array('action' => $this->currentAction); |
||
359 | $params = _elgg_services()->hooks->trigger('output', 'ajax', $context, $params); |
||
360 | |||
361 | // Check the requester can accept JSON responses, if not fall back to |
||
362 | // returning JSON in a plain-text response. Some libraries request |
||
363 | // JSON in an invisible iframe which they then read from the iframe, |
||
364 | // however some browsers will not accept the JSON MIME type. |
||
365 | $http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT'); |
||
366 | if (stripos($http_accept, 'application/json') === false) { |
||
367 | header("Content-type: text/plain;charset=utf-8"); |
||
368 | } else { |
||
369 | header("Content-type: application/json;charset=utf-8"); |
||
370 | } |
||
371 | |||
372 | echo json_encode($params); |
||
373 | exit; |
||
374 | } |
||
375 | } |
||
376 | |||
377 | /** |
||
378 | * @see ajax_action_hook |
||
379 | * @access private |
||
380 | */ |
||
381 | public function ajaxActionHook() { |
||
382 | if (elgg_is_xhr()) { |
||
383 | ob_start(); |
||
384 | } |
||
385 | } |
||
386 | |||
387 | /** |
||
388 | * Get all actions |
||
389 | * |
||
390 | * @return array |
||
391 | */ |
||
392 | public function getAllActions() { |
||
393 | return $this->actions; |
||
394 | } |
||
395 | } |
||
396 | |||
397 |
This check looks for type mismatches where the missing type is
false
. This is usually indicative of an error condtion.Consider the follow example
This function either returns a new
DateTime
object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returnedfalse
before passing on the value to another function or method that may not be able to handle afalse
.