/** * 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(); }
/** * 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')); } }
/** * 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')); }
/** * Set the new credentials for the remote API access on the given API * * @param string $credentials The encrypted credentials to set * @param int $api_id The ID of the API row in the DB * * @return boolean True if it saved, or false if it didn't */ public static function set_credentials($credentials, $api_id = null) { if (empty($api_id)) { $apis_data = \V1\Model\APIs::get_api(); $api_id = (int) $apis_data['id']; } $apis_meta_obj = static::query()->where('apis_id', '=', $api_id)->where('key', '=', 'credentials')->get_one(); if (empty($apis_meta_obj)) { $apis_meta_obj = new static(); $apis_meta_obj->apis_id = $api_id; $apis_meta_obj->key = 'credentials'; $apis_meta_obj->value = $credentials; return $apis_meta_obj->save(); } $apis_meta_obj->value = $credentials; return $apis_meta_obj->save(); }
/** * 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; }
/** * Locate the RAML string for the current API * * @return mixed The RAML string, or boolean false if we couldn't locate it. */ protected static function get_raml_string() { $api_data = \V1\Model\APIs::get_api(); return empty($api_data['raml']) ? false : $api_data['raml']; }
/** * 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; }