123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379 |
- <?php
- namespace Elgg;
- /**
- * WARNING: API IN FLUX. DO NOT USE DIRECTLY.
- *
- * Use the elgg_* versions instead.
- *
- * @access private
- *
- * @package Elgg.Core
- * @subpackage Actions
- * @since 1.9.0
- */
- class ActionsService {
-
- /**
- * Registered actions storage
- * @var array
- */
- private $actions = array();
- /**
- * The current action being processed
- * @var string
- */
- private $currentAction = null;
-
- /**
- * @see action
- * @access private
- */
- public function execute($action, $forwarder = "") {
- $action = rtrim($action, '/');
- $this->currentAction = $action;
-
- // @todo REMOVE THESE ONCE #1509 IS IN PLACE.
- // Allow users to disable plugins without a token in order to
- // remove plugins that are incompatible.
- // Login and logout are for convenience.
- // file/download (see #2010)
- $exceptions = array(
- 'admin/plugins/disable',
- 'logout',
- 'file/download',
- );
-
- if (!in_array($action, $exceptions)) {
- // All actions require a token.
- action_gatekeeper($action);
- }
-
- $forwarder = str_replace(_elgg_services()->config->getSiteUrl(), "", $forwarder);
- $forwarder = str_replace("http://", "", $forwarder);
- $forwarder = str_replace("@", "", $forwarder);
- if (substr($forwarder, 0, 1) == "/") {
- $forwarder = substr($forwarder, 1);
- }
-
- if (!isset($this->actions[$action])) {
- register_error(_elgg_services()->translator->translate('actionundefined', array($action)));
- } elseif (!_elgg_services()->session->isAdminLoggedIn() && ($this->actions[$action]['access'] === 'admin')) {
- register_error(_elgg_services()->translator->translate('actionunauthorized'));
- } elseif (!_elgg_services()->session->isLoggedIn() && ($this->actions[$action]['access'] !== 'public')) {
- register_error(_elgg_services()->translator->translate('actionloggedout'));
- } else {
- // To quietly cancel the action file, return a falsey value in the "action" hook.
- if (_elgg_services()->hooks->trigger('action', $action, null, true)) {
- if (is_file($this->actions[$action]['file']) && is_readable($this->actions[$action]['file'])) {
- self::includeFile($this->actions[$action]['file']);
- } else {
- register_error(_elgg_services()->translator->translate('actionnotfound', array($action)));
- }
- }
- }
-
- $forwarder = empty($forwarder) ? REFERER : $forwarder;
- forward($forwarder);
- }
- /**
- * Include an action file with isolated scope
- *
- * @param string $file File to be interpreted by PHP
- * @return void
- */
- protected static function includeFile($file) {
- include $file;
- }
-
- /**
- * @see elgg_register_action
- * @access private
- */
- public function register($action, $filename = "", $access = 'logged_in') {
- // plugins are encouraged to call actions with a trailing / to prevent 301
- // redirects but we store the actions without it
- $action = rtrim($action, '/');
-
- if (empty($filename)) {
-
- $path = _elgg_services()->config->get('path');
- if ($path === null) {
- $path = "";
- }
-
- $filename = $path . "actions/" . $action . ".php";
- }
-
- $this->actions[$action] = array(
- 'file' => $filename,
- 'access' => $access,
- );
- return true;
- }
-
- /**
- * @see elgg_unregister_action
- * @access private
- */
- public function unregister($action) {
- if (isset($this->actions[$action])) {
- unset($this->actions[$action]);
- return true;
- } else {
- return false;
- }
- }
- /**
- * @see validate_action_token
- * @access private
- */
- public function validateActionToken($visible_errors = true, $token = null, $ts = null) {
- if (!$token) {
- $token = get_input('__elgg_token');
- }
-
- if (!$ts) {
- $ts = get_input('__elgg_ts');
- }
- $session_id = _elgg_services()->session->getId();
-
- if (($token) && ($ts) && ($session_id)) {
- // generate token, check with input and forward if invalid
- $required_token = $this->generateActionToken($ts);
-
- // Validate token
- $token_matches = _elgg_services()->crypto->areEqual($token, $required_token);
- if ($token_matches) {
- if ($this->validateTokenTimestamp($ts)) {
- // We have already got this far, so unless anything
- // else says something to the contrary we assume we're ok
- $returnval = _elgg_services()->hooks->trigger('action_gatekeeper:permissions:check', 'all', array(
- 'token' => $token,
- 'time' => $ts
- ), true);
- if ($returnval) {
- return true;
- } else if ($visible_errors) {
- register_error(_elgg_services()->translator->translate('actiongatekeeper:pluginprevents'));
- }
- } else if ($visible_errors) {
- // this is necessary because of #5133
- if (elgg_is_xhr()) {
- register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
- } else {
- register_error(_elgg_services()->translator->translate('actiongatekeeper:timeerror'));
- }
- }
- } else if ($visible_errors) {
- // this is necessary because of #5133
- if (elgg_is_xhr()) {
- register_error(_elgg_services()->translator->translate('js:security:token_refresh_failed', array(_elgg_services()->config->getSiteUrl())));
- } else {
- register_error(_elgg_services()->translator->translate('actiongatekeeper:tokeninvalid'));
- }
- }
- } else {
- $req = _elgg_services()->request;
- $length = $req->server->get('CONTENT_LENGTH');
- $post_count = count($req->request);
- if ($length && $post_count < 1) {
- // The size of $_POST or uploaded file has exceed the size limit
- $error_msg = _elgg_services()->hooks->trigger('action_gatekeeper:upload_exceeded_msg', 'all', array(
- 'post_size' => $length,
- 'visible_errors' => $visible_errors,
- ), _elgg_services()->translator->translate('actiongatekeeper:uploadexceeded'));
- } else {
- $error_msg = _elgg_services()->translator->translate('actiongatekeeper:missingfields');
- }
- if ($visible_errors) {
- register_error($error_msg);
- }
- }
- return false;
- }
- /**
- * Is the token timestamp within acceptable range?
- *
- * @param int $ts timestamp from the CSRF token
- *
- * @return bool
- */
- protected function validateTokenTimestamp($ts) {
- $timeout = $this->getActionTokenTimeout();
- $now = time();
- return ($timeout == 0 || ($ts > $now - $timeout) && ($ts < $now + $timeout));
- }
- /**
- * @see \Elgg\ActionsService::validateActionToken
- * @access private
- * @since 1.9.0
- * @return int number of seconds that action token is valid
- */
- public function getActionTokenTimeout() {
- if (($timeout = _elgg_services()->config->get('action_token_timeout')) === null) {
- // default to 2 hours
- $timeout = 2;
- }
- $hour = 60 * 60;
- return (int)((float)$timeout * $hour);
- }
- /**
- * @see action_gatekeeper
- * @access private
- */
- public function gatekeeper($action) {
- if ($action === 'login') {
- if ($this->validateActionToken(false)) {
- return true;
- }
- $token = get_input('__elgg_token');
- $ts = (int)get_input('__elgg_ts');
- if ($token && $this->validateTokenTimestamp($ts)) {
- // The tokens are present and the time looks valid: this is probably a mismatch due to the
- // login form being on a different domain.
- register_error(_elgg_services()->translator->translate('actiongatekeeper:crosssitelogin'));
- forward('login', 'csrf');
- }
- // let the validator send an appropriate msg
- $this->validateActionToken();
- } else if ($this->validateActionToken()) {
- return true;
- }
- forward(REFERER, 'csrf');
- }
-
- /**
- * @see generate_action_token
- * @access private
- */
- public function generateActionToken($timestamp) {
- $session_id = _elgg_services()->session->getId();
- if (!$session_id) {
- return false;
- }
- $session_token = _elgg_services()->session->get('__elgg_session');
- return _elgg_services()->crypto->getHmac([(int)$timestamp, $session_id, $session_token], 'md5')
- ->getToken();
- }
-
- /**
- * @see elgg_action_exists
- * @access private
- */
- public function exists($action) {
- return (isset($this->actions[$action]) && file_exists($this->actions[$action]['file']));
- }
-
- /**
- * @see ajax_forward_hook
- * @access private
- */
- public function ajaxForwardHook($hook, $reason, $return, $params) {
- if (elgg_is_xhr()) {
- // always pass the full structure to avoid boilerplate JS code.
- $params = array_merge($params, array(
- 'output' => '',
- 'status' => 0,
- 'system_messages' => array(
- 'error' => array(),
- 'success' => array()
- )
- ));
-
- //grab any data echo'd in the action
- $output = ob_get_clean();
-
- //Avoid double-encoding in case data is json
- $json = json_decode($output);
- if (isset($json)) {
- $params['output'] = $json;
- } else {
- $params['output'] = $output;
- }
-
- //Grab any system messages so we can inject them via ajax too
- $system_messages = _elgg_services()->systemMessages->dumpRegister();
-
- if (isset($system_messages['success'])) {
- $params['system_messages']['success'] = $system_messages['success'];
- }
-
- if (isset($system_messages['error'])) {
- $params['system_messages']['error'] = $system_messages['error'];
- $params['status'] = -1;
- }
- if ($reason == 'walled_garden') {
- $reason = '403';
- }
- $httpCodes = array(
- '400' => 'Bad Request',
- '401' => 'Unauthorized',
- '403' => 'Forbidden',
- '404' => 'Not Found',
- '407' => 'Proxy Authentication Required',
- '500' => 'Internal Server Error',
- '503' => 'Service Unavailable',
- );
- if (isset($httpCodes[$reason])) {
- header("HTTP/1.1 $reason {$httpCodes[$reason]}", true);
- }
- $context = array('action' => $this->currentAction);
- $params = _elgg_services()->hooks->trigger('output', 'ajax', $context, $params);
-
- // Check the requester can accept JSON responses, if not fall back to
- // returning JSON in a plain-text response. Some libraries request
- // JSON in an invisible iframe which they then read from the iframe,
- // however some browsers will not accept the JSON MIME type.
- $http_accept = _elgg_services()->request->server->get('HTTP_ACCEPT');
- if (stripos($http_accept, 'application/json') === false) {
- header("Content-type: text/plain;charset=utf-8");
- } else {
- header("Content-type: application/json;charset=utf-8");
- }
-
- echo json_encode($params);
- exit;
- }
- }
-
- /**
- * @see ajax_action_hook
- * @access private
- */
- public function ajaxActionHook() {
- if (elgg_is_xhr()) {
- ob_start();
- }
- }
- /**
- * Get all actions
- *
- * @return array
- */
- public function getAllActions() {
- return $this->actions;
- }
- }
|