/** * Set the new credentials in the DB. * * @param array $credentials The array of new credentials for the API * @param string $api_name The name of the API to set the credentials for or empty for the current API * * @return bool True on success or false on fail */ public static function set_credentials(array $credentials, $api_name = null) { // Specific API if (!empty($api_name)) { if (($api_id = \V1\Model\APIs::get_api_id($api_name, true)) === false) { return false; } $credentials_encrypted = \Crypt::encode(json_encode($credentials)); \V1\Model\APIsMetaData::set_credentials($credentials_encrypted, $api_id); return true; } // Figure out what to set based on the current request if (\V1\APIRequest::is_static() === true) { $credentials_encrypted = \Crypt::encode(json_encode($credentials)); return \V1\Model\APIsMetaData::set_credentials($credentials_encrypted); } else { $account_data = \V1\Model\Account::get_account(); // Only store them their credentials if the account holder wants us to. if ($account_data['store_credentials'] === 1) { $credentials[\V1\APIRequest::get('api')] = $credentials; if (!empty($account_data['credentials'])) { $account_data['credentials'] = json_decode(\Crypt::decode($account_data['credentials']), true); $account_data['credentials'][\V1\APIRequest::get('api')] = $credentials[\V1\APIRequest::get('api')]; $credentials = $account_data['credentials']; } $credentials_encrypted = \Crypt::encode(json_encode($credentials)); return \V1\Model\AccountsMetaData::set_credentials($account_data['id'], $credentials_encrypted); } return true; } }
/** * 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')); } }
/** * Check if the account has reached the maximum number of API calls it can make globally (not API specific) * * @return boolean True if they're within their limits, false if not */ protected static function check_global_limits() { $account_data = \V1\Model\Account::get_account(); $total_calls = \V1\Usage::get_usage(false); // If they've used up all of their calls, and they aren't unlimited, then they've hit their global limits. if ($total_calls === $account_data['max_calls'] && $account_data['max_calls'] != 0) { return false; } return true; }
/** * Get the ID for a given API name * * @param string $api_name The name of the API * @param bool $public_only Set to true to exclude account owned APIs * * @return mixed The API ID or false on fail */ public static function get_api_id($api_name, $public_only = false) { // Select the chosen one. $api_data = static::query()->where('name', '=', $api_name)->and_where_open()->where('private', '=', 0); if ($public_only === false) { $account_data = \V1\Model\Account::get_account(); $api_data->or_where_open()->where('private', '=', 1)->where('account_id', '=', $account_data['id'])->or_where_close(); } $row = $api_data->and_where_close()->get_one(); if (!empty($row['id'])) { return $row['id']; } return false; }
/** * 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(); } }
/** * Delete all usage statistics, or the usage statistics for an API. * * @param string $api The API to delete the usage statistics for, or leave it empty to remove everything */ public static function delete_usage($api = null) { // Delete everything if (empty($api)) { \Cache::delete('account.' . \Session::get('consumer_key') . 'usage'); /* * Remove the reset time from the DB so that we aren't always resetting it. * Free account don't pay (obviously), so we set their reset timer to 30 days * from now. */ $account_data = \V1\Model\Account::get_account(); $time = null; if ($account_data['access_level'] === 1) { $time = time() + (int) \Config::get('tiers.free.reset_period', 30) * 24 * 60 * 60; } \Debug::dump($time); \V1\Model\Account::set_reset_usage($time); } else { try { // If we have an entry for the API, then delete it. $usage = \Cache::get('account.' . \Session::get('consumer_key') . 'usage'); if (isset($usage[$api])) { unset($usage[$api]); } // Set it without the value for the chosen API. \Cache::set('account.' . \Session::get('consumer_key') . 'usage', $usage); } catch (\CacheNotFoundException $e) { } } }
/** * Add the array of Bit API Hub headers for the call * * @param array $headers The array of existing headers * @return array $headers with the Bit API Hub headers added on */ public static function get_headers(array $headers) { $api = \V1\Model\APIs::get_api(); $account = \V1\Model\Account::get_account(); $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'); } $headers = array_replace($headers, array('User-Agent' => 'API Optimization Engine/V1', 'X-Forwarded-For' => $forwarded_for)); if (\Config::get('engine.send_engine_auth', false) === true) { // If the API hasn't yet received a secret identity, generate one. if (empty($api['secret'])) { $secret = \V1\Model\APIs::set_api_secret($api['id']); } else { $secret = \Crypt::decode($api['secret']); } $headers = array_replace($headers, array('X-AOE-Secret' => $secret, 'X-AOE-Account' => $account['id'], 'X-AOE-Version' => 'V1')); } return $headers; }
/** * 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; }
/** * 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; }
/** * 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; }