/** * Cast a survey to an array for response * * @param Survey survey * * @return array */ public static function cast(Survey $survey) { // Do not add survey_id to vote response unless vote id is ungessable, it would otherwise allow to easily discover surveys $data = array('id' => $survey->id, 'user_id' => $survey->user_id, 'type' => $survey->type, 'title' => $survey->title, 'description' => $survey->description, 'created' => RestUtilities::formatDate($survey->created), 'choices' => $survey->choices, 'permissions' => $survey->can); if ($survey->owner->is(Auth::user()) || Auth::isAdmin()) { $data['guests'] = $survey->guests; } return $data; }
/** * Allows to get pad content from etherpad API * @param {PadComponent} $pad The pad to get content * @retunr {string} content returned by the API */ private function exportPadContent($pad) { $cdatas = array('padID' => $pad->pad_name, 'apikey' => $this->etherpadApiKey); $result = RestUtilities::callAPI($this->etherpadURLApi . 'getHTML', RestMethods::GET, $cdatas); if ($result) { if (isset($result->error)) { throw new RestException($result->error); } else { if ($result->code == 0) { return $result->data->html; } } } }
/** * Authentication check. * * @return bool */ public static function isAuthenticated() { if (is_null(self::$isAuthenticated)) { self::$isAuthenticated = false; // Do we have remote authentication data in the request ? if (!array_key_exists('signature', $_GET)) { return false; } if (!array_key_exists('timestamp', $_GET)) { return false; } $application = array_key_exists('remote_application', $_GET) ? $_GET['remote_application'] : null; $uid = array_key_exists('remote_user', $_GET) ? $_GET['remote_user'] : null; if (!$application && !$uid) { return false; } self::$attributes = array(); // Get data $received_signature = $_GET['signature']; $timestamp = (int) $_GET['timestamp']; if ($application) { // Check that application is known $applications = Config::get('auth_remote_applications'); if (!is_array($applications) || !array_key_exists($application, $applications)) { throw new AuthRemoteUknownApplicationException($application); } $application = new RemoteApplication($application, $applications[$application]); } // Check request time to avoid replays $late = time() - $timestamp - 15; if ($late > 0) { throw new AuthRemoteTooLateException($late); } // Get method from headers $method = null; foreach (array('X_HTTP_METHOD_OVERRIDE', 'REQUEST_METHOD') as $k) { if (!array_key_exists($k, $_SERVER)) { continue; } $method = strtolower($_SERVER[$k]); } // Build signed data $signed = $method . '&' . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'] . (array_key_exists('PATH_INFO', $_SERVER) ? $_SERVER['PATH_INFO'] : ''); $args = $_GET; unset($args['signature']); if (count($args)) { $signed .= '?' . implode('&', RestUtilities::flatten($args)); } $input = Request::body(); if ($input) { $signed .= '&' . $input; } // Check signature if ($application) { $secret = $application->secret; } else { // Get user, fail if unknown or no user secret try { $user = User::fromId($uid); } catch (UserNotFoundException $e) { throw new AuthRemoteUserRejectedException($uid, 'user not found'); } if (!$user->auth_secret) { throw new AuthRemoteUserRejectedException($user->id, 'no secret set'); } $secret = $user->auth_secret; } $algorithm = Config::get('auth_remote_signature_algorithm'); if (!$algorithm) { $algorithm = 'sha1'; } $signature = hash_hmac($algorithm, $signed, $secret); if ($received_signature !== $signature) { throw new AuthRemoteSignatureCheckFailedException($signed, $secret, $received_signature, $signature); } // Register user id if given if ($uid) { self::$attributes['uid'] = $uid; } // Register admin level if asked for and enabled if ($application) { self::$isAdmin = $application->isAdmin; self::$application = $application; self::$attributes['remote_application'] = $application->name; } self::$isAuthenticated = true; } return self::$isAuthenticated; }
/** * Cast a User to an array for response * * @param User user * * @return array */ public static function cast(User $user) { return array('id' => $user->id, 'additional_attributes' => $user->additional_attributes, 'created' => RestUtilities::formatDate($user->created), 'last_activity' => RestUtilities::formatDate($user->last_activity), 'lang' => $user->lang, 'remote_config' => Config::get('auth_remote_user_enabled') ? $user->remote_config : null); }
/** * Cast a Vote to an array for response * * @param Vote vote * * @return array */ public static function cast(Vote $vote) { // Do not add survey_id to vote response unless vote id is ungessable, it would otherwise allow to easily discover surveys return array('id' => $vote->id, 'user_id' => $vote->user_id, 'created' => RestUtilities::formatDate($vote->created), 'updated' => is_null($vote->updated) ? null : RestUtilities::formatDate($vote->updated), 'answers' => $vote->answers); }
/** * Process the request * * @throws lots of various exceptions */ public static function process() { try { @session_start(); // If undergoing maintenance report it as an error if (Config::get('maintenance')) { throw new RestUndergoingMaintenanceException(); } // Split request path to get tokens $path = array(); if (array_key_exists('PATH_INFO', $_SERVER)) { $path = array_filter(explode('/', $_SERVER['PATH_INFO'])); } // Get method from possible headers $method = null; foreach (array('X_HTTP_METHOD_OVERRIDE', 'REQUEST_METHOD') as $k) { if (!array_key_exists($k, $_SERVER)) { continue; } $method = strtolower($_SERVER[$k]); } // Record called method (for log), fail if unknown if (!in_array($method, array('get', 'post', 'put', 'delete'))) { throw new RestMethodNotAllowedException(); } // Get endpoint (first token), fail if none $endpoint = array_shift($path); if (!$endpoint) { throw RestEndpointNotFound(); } // Request data accessor self::$request = new RestRequest($method, $endpoint, $path); // Because php://input can only be read once for PUT requests we rely on a shared getter $input = Request::body(); // Get request content type from possible headers $type = array_key_exists('CONTENT_TYPE', $_SERVER) ? $_SERVER['CONTENT_TYPE'] : null; if (!$type && array_key_exists('HTTP_CONTENT_TYPE', $_SERVER)) { $type = $_SERVER['HTTP_CONTENT_TYPE']; } // Parse content type $type_parts = array_map('trim', explode(';', $type)); $type = array_shift($type_parts); self::$request->properties['type'] = $type; $type_properties = array(); foreach ($type_parts as $part) { $part = array_map('trim', explode('=', $part)); if (count($part) == 2) { self::$request->properties[$part[0]] = $part[1]; } } Logger::debug('Got "' . $method . '" request for endpoint "' . $endpoint . '/' . implode('/', $path) . '" with ' . strlen($input) . ' bytes payload'); // Parse body switch ($type) { case 'text/plain': self::$request->rawinput = trim(Utilities::sanitizeInput($input)); break; case 'application/octet-stream': // Don't sanitize binary input ! self::$request->rawinput = $input; break; case 'application/x-www-form-urlencoded': $data = array(); parse_str($input, $data); self::$request->input = (object) Utilities::sanitizeInput($data); break; case 'application/json': default: self::$request->input = json_decode(trim(Utilities::sanitizeInput($input))); } // Get authentication state (fills auth data in relevant classes) Auth::isAuthenticated(); if (Auth::isRemoteApplication()) { // Remote applications must honor ACLs $application = AuthRemote::application(); if (!$application->allowedTo($method, $endpoint)) { throw new RestNotAllowedException(); } } else { if (Auth::isRemoteUser()) { // Nothing peculiar to do } else { if (in_array($method, array('post', 'put', 'delete'))) { // SP or Guest, lets do XSRF check $token_name = 'HTTP_X_SECURITY_TOKEN'; $token = array_key_exists($token_name, $_SERVER) ? $_SERVER[$token_name] : ''; if ($method == 'post' && array_key_exists('security-token', $_POST)) { $token = $_POST['security-token']; } if (!$token || !Utilities::checkSecurityToken($token)) { throw new RestXSRFTokenInvalidException($token); } } } } // JSONP specifics if (array_key_exists('callback', $_GET) && $method != 'get') { throw new RestJSONPonlyGETException(); } // Get response filters foreach ($_GET as $k => $v) { switch ($k) { case 'count': case 'startIndex': if (preg_match('`^[0-9]+$`', $v)) { self::$request->{$k} = (int) $v; } break; case 'format': break; case 'filterOp': if (is_array($v)) { foreach ($v as $p => $f) { self::$request->filterOp[$p] = array(); foreach (array('equals', 'startWith', 'contains', 'present') as $k) { if (array_key_exists($k, $f)) { self::$request->filterOp[$p][$k] = $f[$k]; } } } } break; case 'sortOrder': if (in_array($v, array('ascending', 'descending'))) { self::$request->sortOrder = $v; } break; case 'updatedSince': // updatedSince takes ISO date, relative N days|weeks|months|years format and epoch timestamp (UTC) $updatedSince = null; if (preg_match('`^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-][0-9]{2}:[0-9]{2})$`', $v)) { // ISO date $localetz = new DateTimeZone(Config::get('default_timezone')); $offset = $localetz->getOffset(new DateTime($v)); $updatedSince = strtotime($v) + $offset; } else { if (preg_match('`^([0-9]+)\\s*(hour|day|week|month|year)s?$`', $v, $m)) { // Relative N day|days|week|weeks|month|months|year|years format $updatedSince = strtotime('-' . $m[1] . ' ' . $m[2]); } else { if (preg_match('`^[0-9]+$`', $v)) { $updatedSince = (int) $v; } } } // Epoch timestamp if (!$updatedSince || !is_numeric($updatedSince)) { throw new RestUpdatedSinceBadFormatException($updatedSince); } self::$request->updatedSince = $updatedSince; break; } } $event = new Event('rest_request', self::$request); $data = $event->trigger(function () { $request = RestServer::getRequest(); // Forward to handler, fail if unknown or method not implemented $class = ucfirst($request->endpoint) . 'Endpoint'; if (!file_exists(APPLICATION_BASE . '/classes/endpoints/' . $class . '.class.php') && !file_exists(APPLICATION_BASE . '/classes/core/endpoints/' . $class . '.class.php')) { throw new RestEndpointNotFoundException(); } if (!method_exists($class, $request->method)) { throw new RestMethodNotImplementedException(); } Logger::debug('Forwarding call to ' . $class . '::' . $request->method . '() handler'); return call_user_func_array($class . '::' . $request->method, $request->path); }); Logger::debug('Got data to send back'); // Output data if (array_key_exists('callback', $_GET)) { header('Content-Type: text/javascript'); $callback = preg_replace('`[^a-z0-9_\\.-]`i', '', $_GET['callback']); echo $callback . '(' . json_encode($data) . ');'; exit; } if (array_key_exists('iframe_callback', $_GET)) { header('Content-Type: text/html'); $callback = preg_replace('`[^a-z0-9_\\.-]`i', '', $_GET['iframe_callback']); echo '<html><body><script type="text/javascript">window.parent.' . $callback . '(' . json_encode($data) . ');</script></body></html>'; exit; } header('Content-Type: application/json'); if ($method == 'post' && $data) { RestUtilities::sendResponseCode(201); if (substr($data['path'], 0, 1) != '/') { $data['path'] = '/' . $data['path']; } header('Location: ' . Config::get('application_url') . 'rest.php' . $data['path']); $data = $data['data']; } echo json_encode($data); } catch (Exception $e) { // Return exceptions as HTTP errors $code = $e->getCode(); if ($code < 400 || $code >= 600) { $code = 500; } RestUtilities::sendResponseCode($code); header('Content-Type: application/json'); echo json_encode(array('message' => $e->getMessage(), 'uid' => method_exists($e, 'getUid') ? $e->getUid() : null, 'details' => method_exists($e, 'getDetails') ? $e->getDetails() : null)); } }
/** * Allows to know if etherpad is avaibable * @return boolean Ture if available, false otherwhise */ public static function isEtherpadAvailable() { $apiUrl = Config::get('etherpad-url'); $apiKey = Config::get('etherpad-apikey'); $available = false; if ($apiUrl && $apiKey && self::getByApiKey($apiKey)) { $cdatas = array('apikey' => $apiKey); $result = RestUtilities::callAPI($apiUrl . 'listAllPads', RestMethods::GET, $cdatas); if ($result && isset($result->code) && $result->code === 0) { $available = true; } } return $available; }