/** * Set an API statistic in the DB * * @param int $code The HTTP status code from the request or cache * @param string $uri The dynamic call URL or the static call name * @param string $api The API to set the stats for * * @return bool True if the stat was added/updated successfully, or false if it wasn't. */ public static function set_stat($code, $uri, $api = null, $is_static = null) { // Save queries if we don't need the stats. if (\Config::get('engine.track_usage_stats', false) === false) { return true; } $api_name = $api === null ? \V1\APIRequest::get('api') : $api; $is_static = $is_static === null ? \V1\APIRequest::is_static() : $is_static; $api = \V1\Model\APIs::get_api($api_name); // Do we have a stat entry for this timeframe? $existing_api_stats_obj = static::query()->where('apis_id', '=', $api['id'])->where('code', '=', (int) $code)->where('call', '=', $uri)->where('created_at', '>', time() - (int) \Config::get('api_stats_increment', 30) * 60)->get_one(); // If we have a row, update it. if (!empty($existing_api_stats_obj)) { $existing_api_stats_obj->count++; return $existing_api_stats_obj->save(); } // Add the new entry. $api_stats_object = new static(); $api_stats_object->apis_id = $api['id']; $api_stats_object->code = $code; $api_stats_object->count = 1; $api_stats_object->call = $uri; $api_stats_object->is_static = intval($is_static); return $api_stats_object->save(); }
/** * The main entry point for V1 of the API engine * * @return string The JSON encoded string for the loader to parse */ public function action_index() { // Set up our APIRequest object if (is_array($setup = \V1\APIRequest::setup())) { return $this->response_format(); } // Validate the request if (is_array($validate_request = \V1\ValidateRequest::run())) { return $this->response_format($validate_request); } // Validate a Data Call if (\V1\APIRequest::get('data-call', false) !== false) { if (is_array($data_call = \V1\Call\DataCall::run()) || is_string($data_call)) { // HTML only Data Calls if (\Session::get('response_format', false) === 'html' && is_string($data_call)) { return $data_call; } // Non-HTML Data Calls return $this->response_format($data_call); } } // Are we processing an API call? if (\V1\APIRequest::get('api', false) !== false) { $runcall = new \V1\RunCall(); if (is_array($response = $runcall->ignite())) { return $this->response_format($response); } } // Nothing happened, so return an error. return $this->response_format(\Utility::format_error(500)); }
/** * Get the row of API data from the DB. * * @param string $api The name of the API to pull data for or null to grab the posted API * @return array The array of data for the API from the DB, or an empty array */ public static function get_api($api = null) { $api = empty($api) ? \V1\APIRequest::get('api') : $api; $account_data = \V1\Model\Account::get_account(); // Select the chosen one. $api_data = static::query()->where('name', '=', $api)->and_where_open()->where('private', '=', 0)->or_where_open()->where('private', '=', 1)->where('account_id', '=', $account_data['id'])->or_where_close()->and_where_close()->get_one(); if (!empty($api_data)) { // Default $return = array(); // Turn the object into an array foreach ($api_data as $key => $value) { // Don't add the metadata object if (!in_array($key, array('apistats', 'metadata'))) { $return[$key] = $value; } } // Grab the metadata and stick it on the return array, too. foreach ($api_data->metadata as $metadata) { $return[$metadata->key] = $metadata->value; } // Spit out all of that data. return $return; } else { return array(); } }
/** * Configure a dynamic call * * @return mixed The \V1\APICall object or the error array on fail */ public static function configure_call() { // Dynamic calls don't work through JS. if (\Session::get('public', true) === true) { return \Utility::format_error(400, \V1\Err::NO_JS_CALLS, \Lang::get('v1::errors.no_js_calls')); } // We need API configuration data. if (!is_array(\V1\APIRequest::get('configure', false))) { return \Utility::format_error(400, \V1\Err::MISSING_CONFIGURE, \Lang::get('v1::errors.missing_configure')); } // For some reason we can't parse the RAML, so we throw a 500 error. if (($api_def = \V1\RAML::parse()) === false) { return \Utility::format_error(500); } elseif (is_array($api_def)) { // Specific error return $api_def; } if (is_string($uri = \V1\APIRequest::get('configure.uri')) && is_string($method = \V1\APIRequest::get('configure.method'))) { $api_call = null; // Custom calls if (\V1\APIRequest::get('api') === 'custom') { if (is_string($url = \V1\APIRequest::get('configure.url')) && !empty($url)) { $api_call = \V1\APICall::forge($api_def, $method, $uri, null, true, $url); } return \Utility::format_error(400, \V1\Err::NO_URL, \Lang::get('v1::errors.no_url')); } $api_data = \V1\Model\APIs::get_api(); try { // Is it a valid resource? $api_def->getResourceByUri($uri)->getMethod($method); // We'll validate the call unless both the API provider and calling script deny that protection. $custom_dynamic = false; if ($api_data['force_validation'] === 0 && \V1\APIRequest::get('no-validate', false) === true) { $custom_dynamic = true; } $api_call = \V1\APICall::forge($api_def, $method, $uri, null, $custom_dynamic); } catch (\Raml\Exception\BadParameter\ResourceNotFoundException $e) { // Does the API Provider allow for unconfigured static calls on their server? if ($api_data['allow_custom_dynamic'] === 1) { $api_call = \V1\APICall::forge($api_def, $method, $uri, null, true); } } if (is_object($api_call)) { if (!empty($api_call->get_errors())) { // Errors from \APICall return $api_call->get_errors(); } else { // Return the \APICall object return $api_call; } } } // Not found return \Utility::format_error(400, \V1\Err::BAD_DYNAMIC, \Lang::get('v1::errors.bad_dynamic')); }
/** * Get the row of Data Call data from the DB. * * @param string $data_call The name of the Data Call to pull data for or null to grab the posted Data Call * @return array The array of data for the Data Call from the DB, or an empty array */ public static function get_data_call($data_call = null) { $data_call = empty($data_call) ? \V1\APIRequest::get('data-call') : $data_call; $account_data = \V1\Model\Account::get_account(); // Select the chosen one. $data_call_data = static::query()->where('name', '=', $data_call)->and_where_open()->where('account_id', '=', 0)->or_where('account_id', '=', $account_data['id'])->and_where_close()->get_one(); if (!empty($data_call_data)) { $return = array(); // Turn the object into an array (You can't just cast it to an array unfortunately.) foreach ($data_call_data as $key => $value) { $return[$key] = $value; } return $return; } else { return array(); } }
/** * Validate the request to make sure we have trusted and sufficient data. * * @return mixed True on success, or an array of error data on fail */ public static function run() { /** * Verify that we have at least an "api" or "data-call" token. */ if (\V1\APIRequest::get('api', false) === false && \V1\APIRequest::get('data-call', false) === false) { return \Utility::format_error(400, \V1\Err::BAD_BODY, \Lang::get('v1::errors.bad_body')); } /** * Easter egg processing plant */ if (\V1\APIRequest::get('api') === 'I\'m a teapot') { return \Utility::format_error(418, \V1\Err::IM_A_TEAPOT, str_replace("\t", '', \Lang::get('v1::errors.im_a_teapot'))); } /** * AUTHORIZATION */ // Once we've authenticated to start running calls from one Data Call, we don't authenticate again. if (\Session::get('data_call', false) === false) { // If they failed to authenticate, then issue a 401 unauthorized error. if (\V1\Account::authenticate() === false) { // Log the failure. \Log::logger('INFO', 'AUTHORIZE:FAIL', \Lang::get('log.authorize_fail'), __METHOD__, array('consumer_key' => \Session::get('consumer_key', 'NOT SET'), 'public_mode' => \Session::get('public', 'NOT SET'))); return \Utility::format_error(401); } // Log the success. \Log::logger('INFO', 'AUTHORIZE:SUCCESS', \Lang::get('log.authorize_success'), __METHOD__, array('consumer_key' => \Session::get('consumer_key', 'NOT SET'), 'public_mode' => \Session::get('public', 'NOT SET'))); /** * DOWNGRADE PROCESSING */ \V1\Account::downgrade(); } /** * GLOBAL LIMITS */ if (static::check_global_limits() === false) { return \Utility::format_error(429, \V1\Err::MAXED_OUT_LIMITS, \Lang::get('v1::errors.maxed_out_limits')); } return true; }
/** * Call the remote API server * * @param \V1\APICall $apicall_obj The APICall object we're using to make calls * @return array The array of data ready for display (Response array or error array) */ public function make_the_call(\V1\APICall $apicall_obj) { /** * RUNCLE RICK'S RAD RUN CALL :) */ $api = \V1\Model\APIs::get_api(); $account = \V1\Model\Account::get_account(); /* * When we make a call from the local server, we'll get the localhost IP. If that's the case, * we'll set our public IP. DO NOT use X-Forwarded-For in the request headers to us. It's unreliable. * We'll still set our X-Forwarded-For in case the API provider wishes to use it. */ $forwarded_for = \Input::real_ip('0.0.0.0', true); if ($internal_call = \Utility::is_internal_call()) { $forwarded_for = \Config::get('engine.call_test_ip'); } /* * Add our own headers to allow for authenticating our server and customers. We overwrite any * of these headers that were specified by the API Provider through RAML, or by the developer * through thier configuration. */ $headers = static::get_headers($apicall_obj->get_headers()); $call = array('url' => $apicall_obj->get_url(), 'method' => $apicall_obj->get_method(), 'headers' => $headers, 'body' => $apicall_obj->get_method_params(), 'body-type' => $apicall_obj->get_body_type()); if (\Fuel::$env !== 'production' && \Config::get('engine.dev_disable_live_calls', false) === true) { /** * In dev mode we can disable calls to the remote server. Feel free to change the * dummy response to whatever you'd like to. */ $response = array('status' => 200, 'headers' => array('X-Dev-Mode' => 'Dummy header'), 'body' => array('dummy_key' => 'dummy_val')); return \Utility::format_response(200, $response); } else { /* * We'll see if anyone got a cached entry into our system while we were configuring stuff. * That way we'll save time. */ if (\V1\APIRequest::is_static() && is_array($cached_data = \V1\Call\StaticCall::get_call_cache())) { // Return the response-formatted data from the cached entry. return $cached_data; } $queued = \V1\Socket::forge()->queue_call(\V1\APIRequest::get('api'), $call, $apicall_obj); } // Non-Data Calls grab the request right away. if (\Session::get('data_call', false) === false) { if ($queued === false) { // Server unavailable return \Utility::format_error(503, \Err::SERVER_ERROR, \Lang::get('v1::errors.remote_unavailable')); } // Pull the results. $result = \V1\Socket::forge()->get_results(); if (is_array($result)) { // We only have one call. return $result[\V1\APIRequest::get('api')][0]; } else { // If the request failed with false, it means that all streams timed out. return \Utility::format_error(500); } } $dc_response = array('status' => 200, 'headers' => array(), 'body' => \V1\Constant::QUEUED_CALL); // In Data Call mode we just signify that we've queued the call. return \Utility::format_response(200, $dc_response); }
/** * Check the speed limits of an API to make sure that the account isn't calling the API more than it's * allowed to in the specified time frame. * * @return mixed True if the call is within the speed limits, or the error array if it isn't */ private static function check_usage_limits() { $api_data = \V1\Model\APIs::get_api(\V1\APIRequest::get('api')); $account_data = \V1\Model\Account::get_account(); $package_limits = \V1\RAML::parse_package_limits(); /* * Are there any limits on the API for our package level? API limits are imposed * by Bit API Hub, not the API provider. It helps to limit the amount of bandwidth * we allot for each call. The heavier the call, the less calls people can make. */ if (is_array($package_limits) && array_key_exists('level' . $account_data['access_level'], $package_limits) && !empty($usage = \V1\Usage::get_usage(\V1\APIRequest::get('api')))) { // Loop the allotments of API calls per time period foreach ($package_limits['level' . $account_data['access_level']] as $time_period => $allotment) { // If we have a valid log for the time period, and it's already maxed out, fail the check. if (isset($usage[$time_period]) && $usage[$time_period]['count'] == $allotment && \Utility::subtract_seconds($time_period) <= $usage[$time_period]['ts'] && $usage[$time_period]['ts'] <= time()) { return \Utility::format_error(429, \V1\Err::TOO_MANY_REQUESTS, \Lang::get('v1::errors.too_many_requests', array('allotment' => number_format($allotment), 'period' => $time_period))); } } } // We're not breaking the speed limit, so shush. return true; }
/** * Decode the credentials from the DB data array * * @param array $array The array of data from the DB for the account or API to find the credentials for * @return array The array of decoded credentials, or an empty array if none exist */ protected static function decode_credentials(array $array) { if (isset($array['credentials']) && is_array($credentials = json_decode(\Crypt::decode($array['credentials']), true))) { if (\V1\APIRequest::is_static()) { // Bit API Hub credentials for the API return $credentials; } else { // Get the credentials for the specific API if (!empty($credentials[\V1\APIRequest::get('api')])) { return $credentials[\V1\APIRequest::get('api')]; } } } // No credentials return array(); }
/** * Run the request * * @param \Raml\SecurityScheme $securityscheme_obj The security scheme to process the call data for * @param \V1\APICall $apicall_obj The APICall object * * @return mixed The object we just completed or an array describing the next step in the security process */ public function run(\Raml\SecurityScheme $securityscheme_obj, \V1\APICall $apicall_obj) { // If the customer enters an authorization header, then we bail out of the security call. if (\V1\APIRequest::is_static() === false && !empty($custom_headers = \V1\APIRequest::get('configure.headers', null)) && !empty($custom_headers['Authorization'])) { return true; } $this->apicall = $apicall_obj; /** * GATHER THE NEEDED DATA */ $credentials = $apicall_obj->get_credentials(); $settings = $securityscheme_obj->getSettings(); $api_def = $apicall_obj->get_api_def(); $api_url = $apicall_obj->get_url(); $method = $apicall_obj->get_method(); $this->credentials = $credentials; // Digest URI $parsed_url = parse_url($api_url); $digest_uri = empty($parsed_url['path']) ? '/' : $parsed_url['path']; if (!empty($parsed_url['query'])) { $digest_uri .= '?' . $parsed_url['query']; } $algorithm = empty($settings['algorithm']) ? 'md5' : \Str::lower($settings['algorithm']); // Make sure that we have the required data. if (empty($credentials['DIGEST_USERNAME']) || empty($credentials['DIGEST_PASSWORD']) || empty($settings['realm'])) { $this->error = true; return; } $this->realm = $settings['realm']; $this->username = $credentials['DIGEST_USERNAME']; // Find our nonce. if (empty($credentials['DIGEST_NONCE'])) { // Beg for nonces $this->parse_www_auth_remote($api_url); if (is_array($this->www_data) && !empty($this->www_data['nonce'])) { $this->nonce = $this->www_data['nonce']; } else { $this->error = true; return; } } else { $this->nonce = $credentials['DIGEST_NONCE']; } // We save this value in the DB. $credentials['DIGEST_NONCE'] = $this->nonce; // Figure out if we've used the current nonce before. if (!empty($settings['qop'])) { // We may have "auth" or "auth-int" or "auth,auth-int" or "auth-int,auth" if (substr_count(\Str::lower($settings['qop']), 'auth-int') > 0) { $this->qop = 'auth-int'; } else { $this->qop = 'auth'; } /** * We have a qop, so we need to figure out how many times we've sent a request with * the current nonce. (Including the current request) * * @link http://www.ietf.org/rfc/rfc2617.txt * Look up "nonce-count" */ if (empty($credentials['DIGEST_NONCE_COUNT'])) { $credentials['DIGEST_NONCE_COUNT'] = 0; } $this->nonce_count = ++$credentials['DIGEST_NONCE_COUNT']; } // Do we need to send the "opaque" param? if (!empty($settings['opaque'])) { // It stays the same for the requester forevermore. if ($settings['opaque'] === 'same') { // We have the value on file. (Dynamic calls only) if (!empty($credentials['DIGEST_OPAQUE'])) { $this->opaque = $credentials['DIGEST_OPAQUE']; } } // If it isn't set to "same" or "changes," then we have a static value. if ($settings['opaque'] !== 'changes') { $this->opaque = $settings['opaque']; } // We couldn't find the value, so we pull it from the header data of a new request. if (empty($this->opaque)) { // If we never contacted the remote server, do that now. if ($this->www_data === false) { $this->parse_www_auth_remote($api_url); } if (is_array($this->www_data) && !empty($this->www_data['opaque'])) { $this->opaque = $this->www_data['opaque']; // We'll save it since it'll always be the same. if ($settings['opaque'] === 'same') { $credentials['DIGEST_OPAQUE'] = $this->opaque; } } else { // We've called the remote server and it didn't have the data. $this->error = true; return; } } } /* * Increment our nonce counter for the current request with the nonce. (Pardon me while I go get a * bowl of de Chex.) */ $this->nonce_count = dechex($this->nonce_count); /** * Format the nonce count as specified in section 3.2.2 of the RFC2617. * * @link http://www.ietf.org/rfc/rfc2617.txt */ if (($padding = 8 - strlen($this->nonce_count)) > 0) { $this->nonce_count = str_repeat(0, $padding) . $this->nonce_count; } // Reliable client nonce $this->cnonce = \Utility::get_nonce(); /** * START COMPILING THE HEADER */ // MD5 $this->ha1 = md5($credentials['DIGEST_USERNAME'] . ':' . $this->realm . ':' . $credentials['DIGEST_PASSWORD']); // MD5-sess if ($algorithm === 'md5-sess') { $this->ha1 = md5($this->ha1 . ':' . $this->nonce . ':' . $this->cnonce); } \V1\Keyring::set_credentials($credentials); // We've finished for now. We'll configure more just before we send the request. }
/** * Validate that the required parameters have values * * @param array $params The array of parameters to check * @param string $type The name of the \V1\APIRequest::get('configure') key to use for user data (can be * null for static calls) * * @return mixed The array of finalized parameters, or false on fail */ protected function validate_params(array $params, $type = null) { $param_array = array(); $error_code = 500; if (\V1\APIRequest::is_static() === false) { // Dynamic call data $configure = \V1\APIRequest::get('configure'); $param_array = isset($configure[$type]) ? $configure[$type] : array(); $error_code = 400; } // Skip the validation on non-validation requests if ($this->custom_dynamic === false) { // Loop and parse foreach ($params as $param_name => $namedparameter_obj) { // Use the default if it exists if we have an empty value. if (!array_key_exists($param_name, $param_array) && !empty($default = $namedparameter_obj->getDefault())) { $param_array[$param_name] = $default; } // Further validate the param based on RAML specs (Including checking if it's required) try { // Set it to a separate variable to avoid empty values getting sent to the server. $validate = empty($param_array[$param_name]) ? null : $param_array[$param_name]; $namedparameter_obj->validate($validate); } catch (\Raml\Exception\ValidationException $e) { $this->errors = \Utility::format_error($error_code, \V1\Err::BAD_PARAM, \Lang::get('v1::errors.bad_param', array('required_param' => $param_name, 'location' => $type))); return false; } } } // Prevent CRLF attacks if (!empty($param_array) && $type === 'headers') { if ($this->check_crlf($param_array) === false) { return false; } } return $param_array; }
/** * Configure a static call * * @return mixed The \V1\APICall object or an error array if the call wasn't found */ public static function configure_call() { if (is_array($static_calls = \V1\RAML::parse_static_calls())) { $api_data = \V1\Model\APIs::get_api(); $account_data = \V1\Model\Account::get_account(); foreach ($static_calls as $call => $call_data) { // The static call data matches the request. if ('/' . \V1\APIRequest::get('static-call') === $call) { // If we can't run the inactive call, then error out. if (isset($call_data['stability']) && $call_data['stability'] === 0 && ($account_data['can_run_inactive'] === 0 || $account_data['id'] !== $api_data['account_id'])) { return \Utility::format_error(403, \V1\Err::DISABLED_STATIC, \Lang::get('v1::errors.disabled_static')); } // Do we have a cached entry? if (is_array($cached_data = \V1\Call\StaticCall::get_call_cache())) { // Set their usage stats. \V1\Usage::set_usage(); // Set the API provider stats \V1\Usage::set_api_stats($cached_data['response']['status']); // Return the response-formatted data from the cached entry. return $cached_data; } // Try to get an APICall object if (($apicall = static::apicall_object($call)) !== false) { return $apicall; } } } // The static call wasn't found. return \Utility::format_error(404, \V1\Err::BAD_STATIC, \Lang::get('v1::errors.bad_static')); } else { // If they've requested a static call when there aren't any for the requested API, give 'em errors! return \Utility::format_error(404, \V1\Err::NO_STATIC, \Lang::get('v1::errors.no_static')); } }
/** * Parse the HTML data * * @param array $template The array of template data from the Data Call call script * @param array $api_responses The array of responses we ran with the Data Call * @param bool $return_html Set to true to return only HTML, not the array of parsed template data * * @return mixed The array of parsed template data, or a string of HTML body contents if $return_html is true */ protected static function process_template(array $template, array $api_responses, $return_html = false) { // Fill out our variables if we have some. (Kind of pointless not to.) if (!empty($template['variables'])) { $variables = array(); foreach ($template['variables'] as $variable => $value_location) { $variables[$variable] = null; // Check if it resolves. if (($value = \Arr::get($api_responses, $value_location, 'BAH_NO_VALUE')) !== 'BAH_NO_VALUE') { // Add the value to the variable so we can replace it. if (is_numeric($value) || is_string($value) || is_null($value)) { $variables[$variable] = (string) $value; } // Boolean doesn't like to get cast to a string properly. if (is_bool($value)) { $variables[$variable] = $value === false ? 'false' : 'true'; } } } // We don't return these. unset($template['variables']); // Replace the variables in the template. if (!empty($variables)) { $template = str_replace(array_keys($variables), array_values($variables), $template); } } $account_data = \V1\Model\Account::get_account(); /** * Add our link back on free accounts, or account who wish to show a link back. While free accounts * could just decide not to show the body, they cannot configure their own Data Calls to place body * content in "css" or "js". Anyone can just preg_replace() away the linkback, but in general, * free accounts will show our link back. */ if (!empty($template['body']) && ($account_data['access_level'] === 1 || $account_data['link_back'] === 1)) { $template['body'] .= \View::forge('linkback', array('color' => \V1\APIRequest::data_call('linkback-color', 'dark')), false)->render(); } // We'll parse the template for them. if ($return_html === true) { $html = null; // CSS should be in <style> tags. $html .= !empty($template['css']) ? $template['css'] . '<body>' : null; // HTML body $html .= !empty($template['body']) ? $template['body'] : null; // JS comes at the end to keep things speedy. $html .= !empty($template['js']) ? $template['js'] : null; return $html; } // Send the template data sectioned out for use. return $template; }
/** * Grab the API access token and key, and return the array of credentials * * @param \OAuth $oauth The OAuth object we're using for authentication * @param array $settings The array of RAML settings * @param array $credentials The array of credentials for our call * * @return array The array of credentials, or an error array when the customer must manually * validate for the second leg, or false on fail */ private function get_access_tokens(\OAuth $oauth, array $settings, array $credentials) { $callback_url = \Config::get('engine.oauth_10a_callback_url', null); if (!empty($settings['callback'])) { $callback_url = $settings['callback']; } // Grab the request token if we didn't just authorize the request. if (empty($credentials['OAUTH_REQUEST_TOKEN']) && empty($credentials['OAUTH_REQUEST_TOKEN_SECRET'])) { try { $request_token_info = $oauth->getRequestToken($settings['requestTokenUri'], $callback_url, $settings['requestTokenMethod']); } catch (\OAuthException $e) { // Something went wrong, so destroy the cache, and return false so it can get fixed. $this->delete_cache(); return false; } $credentials['OAUTH_REQUEST_TOKEN'] = $request_token_info['oauth_token']; $credentials['OAUTH_REQUEST_TOKEN_SECRET'] = $request_token_info['oauth_token_secret']; } if (empty($credentials['OAUTH_VERIFIER'])) { $credentials['OAUTH_VERIFIER'] = null; } // Three legged auth requires manual validation. if ($settings['legs'] === 3) { if (empty($credentials['OAUTH_REQUEST_TOKEN']) || empty($credentials['OAUTH_REQUEST_TOKEN_SECRET']) || empty($credentials['OAUTH_VERIFIER'])) { if (strpos($settings['authorizationUri'], '?') === false) { $settings['authorizationUri'] .= '?'; } else { $settings['authorizationUri'] .= '&'; } // Save the request tokens. $this->set_cache($credentials); /* * Only non-static calls may show the authorization URL so as to not mislead people into * authorizing their accounts for everyone connected to the API hub. */ if (\V1\APIRequest::is_static() === false) { // Tell them were to go. return \Utility::format_error(401, \V1\Err::OAUTH1_AUTHORIZE, \Lang::get('v1::errors.oauth1_authorize', array('url' => $settings['authorizationUri'] . 'oauth_token=' . $request_token_info['oauth_token']))); } else { return false; } } } $oauth->setToken($credentials['OAUTH_REQUEST_TOKEN'], $credentials['OAUTH_REQUEST_TOKEN_SECRET']); try { $access_token_info = $oauth->getAccessToken($settings['tokenCredentialsUri'], null, $credentials['OAUTH_VERIFIER'], $settings['tokenCredentialsMethod']); } catch (\OAuthException $e) { // Something went wrong, so destroy the cache, and return false so it can get fixed. $this->delete_cache(); return false; } // Clean up our data, and set the access key and secret in the DB. unset($credentials['OAUTH_REQUEST_TOKEN']); unset($credentials['OAUTH_REQUEST_TOKEN_SECRET']); unset($credentials['OAUTH_VERIFIER']); $credentials['OAUTH_ACCESS_TOKEN'] = $access_token_info['oauth_token']; $credentials['OAUTH_ACCESS_TOKEN_SECRET'] = $access_token_info['oauth_token_secret']; // Keep the credentials cached $this->set_cache($credentials); return $credentials; }
public function test_data_call() { $this->assertSame('my-call', \V1\APIRequest::data_call('data-call')); $this->assertSame(false, \V1\APIRequest::data_call('fake', false)); }
/** * Validate the signature for the call * * @param array $tokens The OAuth tokens from the header * @return boolean True if valid, false if invalid */ protected static function valid_signature($tokens) { $mt = microtime(true); // Decode the signature, or fail if (($decoded_sig = urldecode(base64_decode($tokens['oauth_signature']))) === false) { return false; } // Grab the account data so we have a copy of the customer's secret key. $account_data = \V1\Model\Account::get_account($tokens['oauth_consumer_key']); // If the account is invalid, fail. if (empty($account_data)) { return false; } $secret = \Crypt::decode($account_data['consumer_secret']); // Reconstruct the data to build the signature. $oauth = array('oauth_nonce' => $tokens['oauth_nonce'], 'oauth_timestamp' => $tokens['oauth_timestamp'], 'oauth_consumer_key' => $tokens['oauth_consumer_key'], 'oauth_consumer_secret' => $secret, 'body' => urlencode(urlencode(base64_encode(json_encode(\V1\APIRequest::post_data()))))); ksort($oauth); $oauth_encoded = array(); foreach ($oauth as $key => $value) { $oauth_encoded[] = $key . '=' . $value; } // Now we have the string to make the hash $signed_string = urlencode(implode('&', $oauth_encoded)); // Final product $hash = hash_hmac('sha256', $signed_string, $secret); // If they match, it's valid. return $hash === $decoded_sig; }
/** * Set the usage stats for an API call. API Providers use these for their statistics. * * @param number $status The HTTP status code from the remote server * @param mixed $api_request_data The array of API request data, or null to use \V1\APIRequest::get() * @param mixed $is_static True or false to signify static status, or null to use \V1\APIRequest::is_static() */ public static function set_api_stats($status = 200, $api_request_data = null, $is_static = null) { $api_request_data = $api_request_data === null ? \V1\APIRequest::get() : $api_request_data; $is_static = $is_static === null ? \V1\APIRequest::is_static() : $is_static; if ($is_static === true) { \V1\Model\APIStats::set_stat($status, $api_request_data['static-call'], $api_request_data['api'], $is_static); } else { \V1\Model\APIStats::set_stat($status, $api_request_data['configure']['uri'], $api_request_data['api'], $is_static); } }
/** * Prepare the response to show the customer. We also handle caching and usage. * * @param array $response The response array containing data from the remote server * @param string $api The name of the API we're preparing the response for * * @return array The response formatted string */ public static function prepare_response(array $response, $api_request = null, $is_static = null) { $api_request = empty($api_request) ? \V1\APIRequest::get() : $api_request; $api = $api_request['api']; $is_static = $is_static === null ? \V1\APIRequest::is_static() : $is_static; $api_data = \V1\Model\APIs::get_api($api); if ($is_static === true && $api_data['account_id'] === 0) { $response['headers'] = array(); } else { $response['headers'] = static::sanitize_headers($response['headers']); } $response['body'] = static::convert_body($response['headers'], $response['body']); $response_array = \Utility::format_response(200, $response); $internal_call = \Utility::is_internal_call(); /* * Cache the static call response if it the remote server didn't report an error. * To allow for proper testing, we don't cache calls from the account area or other internal * locations. */ if ($is_static === true && $response['status'] < 300 && $internal_call === false) { $api_call = $is_static === true ? $api_request['static-call'] : null; \V1\Call\StaticCall::set_call_cache($response_array, $api, $api_call); } /* * Log the usage stats if we aren't running a call from the internal API testing system. (Ex. The * account area) */ if ($internal_call === false) { // Set the account usage stats \V1\Usage::set_usage($api); // Set the API provider stats \V1\Usage::set_api_stats($response['status'], $api_request, $is_static); } return $response_array; }