Elgg  Version 1.9
ActionsService.php
Go to the documentation of this file.
1 <?php
2 
15 
20  private $actions = array();
21 
26  private $currentAction = null;
27 
32  public function execute($action, $forwarder = "") {
33  $action = rtrim($action, '/');
34  $this->currentAction = $action;
35 
36  // @todo REMOVE THESE ONCE #1509 IS IN PLACE.
37  // Allow users to disable plugins without a token in order to
38  // remove plugins that are incompatible.
39  // Login and logout are for convenience.
40  // file/download (see #2010)
41  $exceptions = array(
42  'admin/plugins/disable',
43  'logout',
44  'file/download',
45  );
46 
47  if (!in_array($action, $exceptions)) {
48  // All actions require a token.
50  }
51 
52  $forwarder = str_replace(elgg_get_site_url(), "", $forwarder);
53  $forwarder = str_replace("http://", "", $forwarder);
54  $forwarder = str_replace("@", "", $forwarder);
55  if (substr($forwarder, 0, 1) == "/") {
56  $forwarder = substr($forwarder, 1);
57  }
58 
59  if (!isset($this->actions[$action])) {
60  register_error(elgg_echo('actionundefined', array($action)));
61  } elseif (!elgg_is_admin_logged_in() && ($this->actions[$action]['access'] === 'admin')) {
62  register_error(elgg_echo('actionunauthorized'));
63  } elseif (!elgg_is_logged_in() && ($this->actions[$action]['access'] !== 'public')) {
64  register_error(elgg_echo('actionloggedout'));
65  } else {
66  // Returning falsy doesn't produce an error
67  // We assume this will be handled in the hook itself.
68  if (elgg_trigger_plugin_hook('action', $action, null, true)) {
69  if (!include($this->actions[$action]['file'])) {
70  register_error(elgg_echo('actionnotfound', array($action)));
71  }
72  }
73  }
74 
75  $forwarder = empty($forwarder) ? REFERER : $forwarder;
76  forward($forwarder);
77  }
78 
83  public function register($action, $filename = "", $access = 'logged_in') {
84  // plugins are encouraged to call actions with a trailing / to prevent 301
85  // redirects but we store the actions without it
86  $action = rtrim($action, '/');
87 
88  if (empty($filename)) {
89 
90  $path = elgg_get_config('path');
91  if ($path === null) {
92  $path = "";
93  }
94 
95  $filename = $path . "actions/" . $action . ".php";
96  }
97 
98  $this->actions[$action] = array(
99  'file' => $filename,
100  'access' => $access,
101  );
102  return true;
103  }
104 
109  public function unregister($action) {
110  if (isset($this->actions[$action])) {
111  unset($this->actions[$action]);
112  return true;
113  } else {
114  return false;
115  }
116  }
117 
122  public function validateActionToken($visible_errors = true, $token = null, $ts = null) {
123  if (!$token) {
124  $token = get_input('__elgg_token');
125  }
126 
127  if (!$ts) {
128  $ts = get_input('__elgg_ts');
129  }
130 
131  $session_id = _elgg_services()->session->getId();
132 
133  if (($token) && ($ts) && ($session_id)) {
134  // generate token, check with input and forward if invalid
135  $required_token = generate_action_token($ts);
136 
137  // Validate token
138  if ($token == $required_token) {
139  if ($this->validateTokenTimestamp($ts)) {
140  // We have already got this far, so unless anything
141  // else says something to the contrary we assume we're ok
142  $returnval = elgg_trigger_plugin_hook('action_gatekeeper:permissions:check', 'all', array(
143  'token' => $token,
144  'time' => $ts
145  ), true);
146 
147  if ($returnval) {
148  return true;
149  } else if ($visible_errors) {
150  register_error(elgg_echo('actiongatekeeper:pluginprevents'));
151  }
152  } else if ($visible_errors) {
153  // this is necessary because of #5133
154  if (elgg_is_xhr()) {
155  register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url())));
156  } else {
157  register_error(elgg_echo('actiongatekeeper:timeerror'));
158  }
159  }
160  } else if ($visible_errors) {
161  // this is necessary because of #5133
162  if (elgg_is_xhr()) {
163  register_error(elgg_echo('js:security:token_refresh_failed', array(elgg_get_site_url())));
164  } else {
165  register_error(elgg_echo('actiongatekeeper:tokeninvalid'));
166  }
167  }
168  } else {
169  $req = _elgg_services()->request;
170  $length = $req->server->get('CONTENT_LENGTH');
171  $post_count = count($req->request);
172  if ($length && $post_count < 1) {
173  // The size of $_POST or uploaded file has exceed the size limit
174  $error_msg = elgg_trigger_plugin_hook('action_gatekeeper:upload_exceeded_msg', 'all', array(
175  'post_size' => $length,
176  'visible_errors' => $visible_errors,
177  ), elgg_echo('actiongatekeeper:uploadexceeded'));
178  } else {
179  $error_msg = elgg_echo('actiongatekeeper:missingfields');
180  }
181  if ($visible_errors) {
182  register_error($error_msg);
183  }
184  }
185 
186  return false;
187  }
188 
196  protected function validateTokenTimestamp($ts) {
197  $timeout = $this->getActionTokenTimeout();
198  $now = time();
199  return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout));
200  }
201 
208  public function getActionTokenTimeout() {
209  if (($timeout = elgg_get_config('action_token_timeout')) === null) {
210  // default to 2 hours
211  $timeout = 2;
212  }
213  $hour = 60 * 60;
214  return (int)((float)$timeout * $hour);
215  }
216 
221  public function gatekeeper($action) {
222  if ($action === 'login') {
223  if ($this->validateActionToken(false)) {
224  return true;
225  }
226 
227  $token = get_input('__elgg_token');
228  $ts = (int)get_input('__elgg_ts');
229  if ($token && $this->validateTokenTimestamp($ts)) {
230  // The tokens are present and the time looks valid: this is probably a mismatch due to the
231  // login form being on a different domain.
232  register_error(elgg_echo('actiongatekeeper:crosssitelogin'));
233 
234  forward('login', 'csrf');
235  }
236 
237  // let the validator send an appropriate msg
239  } else if ($this->validateActionToken()) {
240  return true;
241  }
242 
243  forward(REFERER, 'csrf');
244  }
245 
250  public function generateActionToken($timestamp) {
251  $site_secret = get_site_secret();
252  $session_id = _elgg_services()->session->getId();
253  // Session token
254  $st = _elgg_services()->session->get('__elgg_session');
255 
256  if (($site_secret) && ($session_id)) {
257  return md5($site_secret . $timestamp . $session_id . $st);
258  }
259 
260  return false;
261  }
262 
267  public function exists($action) {
268  return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file']));
269  }
270 
275  public function ajaxForwardHook($hook, $reason, $return, $params) {
276  if (elgg_is_xhr()) {
277  // always pass the full structure to avoid boilerplate JS code.
278  $params = array_merge($params, array(
279  'output' => '',
280  'status' => 0,
281  'system_messages' => array(
282  'error' => array(),
283  'success' => array()
284  )
285  ));
286 
287  //grab any data echo'd in the action
288  $output = ob_get_clean();
289 
290  //Avoid double-encoding in case data is json
291  $json = json_decode($output);
292  if (isset($json)) {
293  $params['output'] = $json;
294  } else {
295  $params['output'] = $output;
296  }
297 
298  //Grab any system messages so we can inject them via ajax too
299  $system_messages = system_messages(null, "");
300 
301  if (isset($system_messages['success'])) {
302  $params['system_messages']['success'] = $system_messages['success'];
303  }
304 
305  if (isset($system_messages['error'])) {
306  $params['system_messages']['error'] = $system_messages['error'];
307  $params['status'] = -1;
308  }
309 
310  $context = array('action' => $this->currentAction);
311  $params = elgg_trigger_plugin_hook('output', 'ajax', $context, $params);
312 
313  // Check the requester can accept JSON responses, if not fall back to
314  // returning JSON in a plain-text response. Some libraries request
315  // JSON in an invisible iframe which they then read from the iframe,
316  // however some browsers will not accept the JSON MIME type.
317  $http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT');
318  if (stripos($http_accept, 'application/json') === false) {
319  header("Content-type: text/plain");
320  } else {
321  header("Content-type: application/json");
322  }
323 
324  echo json_encode($params);
325  exit;
326  }
327  }
328 
333  public function ajaxActionHook() {
334  if (elgg_is_xhr()) {
335  ob_start();
336  }
337  }
338 
344  public function getAllActions() {
345  return $this->actions;
346  }
347 }
elgg_get_config($name, $site_guid=0)
Get an Elgg configuration value.
generateActionToken($timestamp)
elgg_is_logged_in()
Returns whether or not the user is currently logged in.
Definition: sessions.php:56
elgg_is_xhr()
Checks whether the request was requested via ajax.
Definition: actions.php:237
$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
validateActionToken($visible_errors=true, $token=null, $ts=null)
action_gatekeeper($action)
Validates the presence of action tokens.
Definition: actions.php:134
getAllActions()
Get all actions.
elgg_is_admin_logged_in()
Returns whether or not the viewer is currently logged in and an admin user.
Definition: sessions.php:65
ajaxForwardHook($hook, $reason, $return, $params)
$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
$params
Definition: login.php:72
generate_action_token($timestamp)
Generate an action token.
Definition: actions.php:156
elgg_echo($message_key, $args=array(), $language="")
Given a message key, returns an appropriately translated full-text string.
Definition: languages.php:21
const REFERER
Definition: elgglib.php:2162
_elgg_services()
Definition: autoloader.php:14
elgg echo
Translates a string.
Definition: languages.js:43
elgg_trigger_plugin_hook($hook, $type, $params=null, $returnvalue=null)
Trigger a Plugin Hook and run all handler callbacks registered to that hook:type. ...
Definition: elgglib.php:925
elgg_get_site_url($site_guid=0)
Get the URL for the current (or specified) site.
get_site_secret()
Returns the site secret.
Definition: actions.php:190
$token
system_messages($message=null, $register="success", $count=false)
Queues a message to be displayed.
Definition: elgglib.php:532
validate_action_token($visible_errors=true, $token=null, $ts=null)
Validate an action token.
Definition: actions.php:116
execute($action, $forwarder="")
validateTokenTimestamp($ts)
Is the token timestamp within acceptable range?
elgg register_error
Wrapper function for system_messages.
Definition: elgglib.js:383
$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