Elgg  Version 1.10
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.
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  // Returning falsy doesn't produce an error
68  // We assume this will be handled in the hook itself.
69  if (_elgg_services()->hooks->trigger('action', $action, null, true)) {
70  if (!include($this->actions[$action]['file'])) {
71  register_error(_elgg_services()->translator->translate('actionnotfound', array($action)));
72  }
73  }
74  }
75 
76  $forwarder = empty($forwarder) ? REFERER : $forwarder;
77  forward($forwarder);
78  }
79 
84  public function register($action, $filename = "", $access = 'logged_in') {
85  // plugins are encouraged to call actions with a trailing / to prevent 301
86  // redirects but we store the actions without it
87  $action = rtrim($action, '/');
88 
89  if (empty($filename)) {
90 
91  $path = _elgg_services()->config->get('path');
92  if ($path === null) {
93  $path = "";
94  }
95 
96  $filename = $path . "actions/" . $action . ".php";
97  }
98 
99  $this->actions[$action] = array(
100  'file' => $filename,
101  'access' => $access,
102  );
103  return true;
104  }
105 
110  public function unregister($action) {
111  if (isset($this->actions[$action])) {
112  unset($this->actions[$action]);
113  return true;
114  } else {
115  return false;
116  }
117  }
118 
123  public function validateActionToken($visible_errors = true, $token = null, $ts = null) {
124  if (!$token) {
125  $token = get_input('__elgg_token');
126  }
127 
128  if (!$ts) {
129  $ts = get_input('__elgg_ts');
130  }
131 
132  $session_id = _elgg_services()->session->getId();
133 
134  if (($token) && ($ts) && ($session_id)) {
135  // generate token, check with input and forward if invalid
136  $required_token = generate_action_token($ts);
137 
138  // Validate token
139  $token_matches = _elgg_services()->crypto->areEqual($token, $required_token);
140  if ($token_matches) {
141  if ($this->validateTokenTimestamp($ts)) {
142  // We have already got this far, so unless anything
143  // else says something to the contrary we assume we're ok
144  $returnval = _elgg_services()->hooks->trigger('action_gatekeeper:permissions:check', 'all', array(
145  'token' => $token,
146  'time' => $ts
147  ), true);
148 
149  if ($returnval) {
150  return true;
151  } else if ($visible_errors) {
152  register_error(_elgg_services()->translator->translate('actiongatekeeper:pluginprevents'));
153  }
154  } else if ($visible_errors) {
155  // this is necessary because of #5133
156  if (elgg_is_xhr()) {
157  register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
158  } else {
159  register_error(_elgg_services()->translator->translate('actiongatekeeper:timeerror'));
160  }
161  }
162  } else if ($visible_errors) {
163  // this is necessary because of #5133
164  if (elgg_is_xhr()) {
165  register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
166  } else {
167  register_error(_elgg_services()->translator->translate('actiongatekeeper:tokeninvalid'));
168  }
169  }
170  } else {
171  $req = _elgg_services()->request;
172  $length = $req->server->get('CONTENT_LENGTH');
173  $post_count = count($req->request);
174  if ($length && $post_count < 1) {
175  // The size of $_POST or uploaded file has exceed the size limit
176  $error_msg = _elgg_services()->hooks->trigger('action_gatekeeper:upload_exceeded_msg', 'all', array(
177  'post_size' => $length,
178  'visible_errors' => $visible_errors,
179  ), _elgg_services()->translator->translate('actiongatekeeper:uploadexceeded'));
180  } else {
181  $error_msg = _elgg_services()->translator->translate('actiongatekeeper:missingfields');
182  }
183  if ($visible_errors) {
184  register_error($error_msg);
185  }
186  }
187 
188  return false;
189  }
190 
198  protected function validateTokenTimestamp($ts) {
199  $timeout = $this->getActionTokenTimeout();
200  $now = time();
201  return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout));
202  }
203 
210  public function getActionTokenTimeout() {
211  if (($timeout = _elgg_services()->config->get('action_token_timeout')) === null) {
212  // default to 2 hours
213  $timeout = 2;
214  }
215  $hour = 60 * 60;
216  return (int)((float)$timeout * $hour);
217  }
218 
223  public function gatekeeper($action) {
224  if ($action === 'login') {
225  if ($this->validateActionToken(false)) {
226  return true;
227  }
228 
229  $token = get_input('__elgg_token');
230  $ts = (int)get_input('__elgg_ts');
231  if ($token && $this->validateTokenTimestamp($ts)) {
232  // The tokens are present and the time looks valid: this is probably a mismatch due to the
233  // login form being on a different domain.
234  register_error(_elgg_services()->translator->translate('actiongatekeeper:crosssitelogin'));
235 
236  forward('login', 'csrf');
237  }
238 
239  // let the validator send an appropriate msg
241  } else if ($this->validateActionToken()) {
242  return true;
243  }
244 
245  forward(REFERER, 'csrf');
246  }
247 
252  public function generateActionToken($timestamp) {
253  $site_secret = _elgg_services()->siteSecret->get();
254  $session_id = _elgg_services()->session->getId();
255  // Session token
256  $st = _elgg_services()->session->get('__elgg_session');
257 
258  if ($session_id && $site_secret) {
259  return _elgg_services()->crypto->getHmac($timestamp . $session_id . $st, $site_secret, 'md5');
260  }
261 
262  return false;
263  }
264 
269  public function exists($action) {
270  return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file']));
271  }
272 
277  public function ajaxForwardHook($hook, $reason, $return, $params) {
278  if (elgg_is_xhr()) {
279  // always pass the full structure to avoid boilerplate JS code.
280  $params = array_merge($params, array(
281  'output' => '',
282  'status' => 0,
283  'system_messages' => array(
284  'error' => array(),
285  'success' => array()
286  )
287  ));
288 
289  //grab any data echo'd in the action
290  $output = ob_get_clean();
291 
292  //Avoid double-encoding in case data is json
293  $json = json_decode($output);
294  if (isset($json)) {
295  $params['output'] = $json;
296  } else {
297  $params['output'] = $output;
298  }
299 
300  //Grab any system messages so we can inject them via ajax too
301  $system_messages = system_messages(null, "");
302 
303  if (isset($system_messages['success'])) {
304  $params['system_messages']['success'] = $system_messages['success'];
305  }
306 
307  if (isset($system_messages['error'])) {
308  $params['system_messages']['error'] = $system_messages['error'];
309  $params['status'] = -1;
310  }
311 
312  $context = array('action' => $this->currentAction);
313  $params = _elgg_services()->hooks->trigger('output', 'ajax', $context, $params);
314 
315  // Check the requester can accept JSON responses, if not fall back to
316  // returning JSON in a plain-text response. Some libraries request
317  // JSON in an invisible iframe which they then read from the iframe,
318  // however some browsers will not accept the JSON MIME type.
319  $http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT');
320  if (stripos($http_accept, 'application/json') === false) {
321  header("Content-type: text/plain");
322  } else {
323  header("Content-type: application/json");
324  }
325 
326  echo json_encode($params);
327  exit;
328  }
329  }
330 
335  public function ajaxActionHook() {
336  if (elgg_is_xhr()) {
337  ob_start();
338  }
339  }
340 
346  public function getAllActions() {
347  return $this->actions;
348  }
349 }
350 
elgg_is_xhr()
Checks whether the request was requested via ajax.
Definition: actions.php:216
$context
Definition: add.php:11
get_input($variable, $default=null, $filter_result=true)
Get some input from variables passed submitted through GET or POST.
Definition: input.php:27
action_gatekeeper($action)
Validates the presence of action tokens.
Definition: actions.php:134
execute($action, $forwarder="")
if(!$autoload_available) _elgg_services()
Definition: autoloader.php:20
$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.
generate_action_token($timestamp)
Generate an action token.
Definition: actions.php:156
const REFERER
Definition: elgglib.php:2087
elgg echo
Translates a string.
Definition: languages.js:43
validateActionToken($visible_errors=true, $token=null, $ts=null)
generateActionToken($timestamp)
$token
system_messages($message=null, $register="success", $count=false)
Queues a message to be displayed.
Definition: elgglib.php:416
validate_action_token($visible_errors=true, $token=null, $ts=null)
Validate an action token.
Definition: actions.php:116
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
$access
Definition: save.php:15