Elgg  Version 1.11
ActionsService.php
Go to the documentation of this file.
1 <?php
2 namespace Elgg;
3 
16 
21  private $actions = array();
22 
27  private $currentAction = null;
28 
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 
87  protected static function includeFile($file) {
88  include $file;
89  }
90 
95  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  $action = rtrim($action, '/');
99 
100  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  $this->actions[$action] = array(
111  'file' => $filename,
112  'access' => $access,
113  );
114  return true;
115  }
116 
121  public function unregister($action) {
122  if (isset($this->actions[$action])) {
123  unset($this->actions[$action]);
124  return true;
125  } else {
126  return false;
127  }
128  }
129 
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  } 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  } 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 
204  protected function validateTokenTimestamp($ts) {
205  $timeout = $this->getActionTokenTimeout();
206  $now = time();
207  return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout));
208  }
209 
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 
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 
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);
269  }
270 
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 
298  public function exists($action) {
299  return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file']));
300  }
301 
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  $context = array('action' => $this->currentAction);
342  $params = _elgg_services()->hooks->trigger('output', 'ajax', $context, $params);
343 
344  // Check the requester can accept JSON responses, if not fall back to
345  // returning JSON in a plain-text response. Some libraries request
346  // JSON in an invisible iframe which they then read from the iframe,
347  // however some browsers will not accept the JSON MIME type.
348  $http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT');
349  if (stripos($http_accept, 'application/json') === false) {
350  header("Content-type: text/plain");
351  } else {
352  header("Content-type: application/json");
353  }
354 
355  echo json_encode($params);
356  exit;
357  }
358  }
359 
364  public function ajaxActionHook() {
365  if (elgg_is_xhr()) {
366  ob_start();
367  }
368  }
369 
375  public function getAllActions() {
376  return $this->actions;
377  }
378 }
379 
elgg_is_xhr()
Checks whether the request was requested via ajax.
Definition: actions.php:227
$context
Definition: add.php:11
validateTokenOwnership($token, $timestamp, $session_token= '')
Was the given token generated for the session defined by session_token?
get_input($variable, $default=null, $filter_result=true)
Get some input from variables passed submitted through GET or POST.
Definition: input.php:27
execute($action, $forwarder="")
static includeFile($file)
Include an action file with isolated scope.
elgg_get_session()
Gets Elgg&#39;s session object.
Definition: sessions.php:23
$return
Definition: opendd.php:15
exit
Definition: reorder.php:12
elgg forward
Meant to mimic the php forward() function by simply redirecting the user to another page...
Definition: elgglib.js:419
$timestamp
Definition: date.php:35
$action
$actions
Definition: user_hover.php:12
$params
Definition: login.php:72
getAllActions()
Get all actions.
Save menu items.
const REFERER
Definition: elgglib.php:1995
_elgg_services()
Definition: autoloader.php:14
elgg echo
Translates a string.
Definition: languages.js:43
validateActionToken($visible_errors=true, $token=null, $ts=null)
$token
validateTokenTimestamp($ts)
Is the token timestamp within acceptable range?
elgg register_error
Wrapper function for system_messages.
Definition: elgglib.js:383
ajaxForwardHook($hook, $reason, $return, $params)
$filename
Definition: crop.php:23
clearfix elgg elgg elgg elgg page header
Definition: admin.php:127
$output
Definition: item.php:10
$path
Definition: invalid.php:17
gatekeeper()
Alias of elgg_gatekeeper()
Definition: pagehandler.php:73
$access
Definition: save.php:15
generateActionToken($timestamp, $session_token= '')
Generate a token from a session token (specifying the user), the timestamp, and the site key...