/**
  * 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();
 }
 /**
  * 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();
     }
 }
Example #3
0
 /**
  * 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));
 }
 /**
  * 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'));
     }
 }
 /**
  * Run the Data Call
  * 
  * @return mixed The array of responses and template data, or the HTML template if the format is "html"
  */
 public static function run()
 {
     if (\Session::get('public', true) === true) {
         return \Utility::format_error(400, \V1\Err::NO_JS_CALLS, \Lang::get('v1::errors.no_js_calls'));
     }
     // Make sure the Data Call exists, and is part of their account.
     if (empty($data_call_data = \V1\Model\DataCalls::get_data_call())) {
         return \Utility::format_error(404, \V1\Err::BAD_DATA_CALL, \Lang::get('v1::errors.bad_data_call'));
     }
     $account_data = \V1\Model\Account::get_account();
     // Make sure they are allowed to access it.
     if ($account_data['access_level'] < $data_call_data['min_access_level']) {
         return \Utility::format_error(402, \V1\Err::UPGRADE_REQUIRED, \Lang::get('v1::errors.upgrade_required'));
     }
     // Make sure that the Data Call is enabled or callable.
     if ($data_call_data['active_level'] === 0 && ($account_data['can_run_inactive'] === 0 && $account_data['id'] !== $data_call_data['account_id'])) {
         return \Utility::format_error(403, \V1\Err::DISABLED_DATA_CALL, \Lang::get('v1::errors.disabled_data_call'));
     }
     // Custom Data Calls allow the user to send us their workload (call script) and we'll process it for them.
     if (\V1\APIRequest::get('data-call', false) === 'custom') {
         $call_script = \V1\APIRequest::get('call-script', false);
     } else {
         $call_script = json_decode($data_call_data['call_script'], true);
     }
     // Make sure we have a call script.
     if (empty($call_script) || !is_array($call_script)) {
         return \Utility::format_error(503, \V1\Err::DATA_CALL_MISCONFIG, \Lang::get('v1::errors.data_call_misconfig'));
     }
     // Free accounts may not change their Data Calls, as they may only use public data calls.
     if ($account_data['access_level'] > 1 && is_array($call_options = \V1\APIRequest::data_call('call-options', false))) {
         $call_script = array_replace_recursive($call_script, $call_options);
     }
     /*
      * Set the Data Call flag to bypass things that no longer pertain to further calls, such as
      * authentication to Bit API Hub.
      */
     \Session::set('data_call', true);
     $response = array();
     $template_data = null;
     foreach ($call_script as $key => $call) {
         // If we have template data for widgets, gadgets, graphs, and charts, oh my, then we'll use it later.
         if ($key === 'template') {
             $template_data = $call;
             continue;
         }
         // We need a name. Even custom calls use the "custom" API.
         if (empty($call['api'])) {
             continue;
         }
         // Set the post data as defined in our script.
         \Session::set('posted_data', $call);
         // Make the call.
         $api_call = \Request::forge('v1/index', false)->execute()->response->body;
         // Bad decode
         if (empty($api_call)) {
             $response[$call['api']][] = \Utility::format_error(500);
         }
         // The response cometh forth, not stuck in zein queue.
         if (!empty($api_call[0]['response']['body']) && $api_call[0]['response']['body'] === \V1\Constant::QUEUED_CALL) {
             // Keep the order of calls right proper. :P
             $response[$call['api']][] = \V1\Constant::QUEUED_CALL;
         } else {
             // We have our response, so we set that now.
             $response[$call['api']][] = $api_call[0];
         }
     }
     // If the customer doesn't need any response data, then we don't make them wait while we retrieve the results.
     if (\V1\APIRequest::data_call('no-response', false) === true) {
         $response = array('status' => 200, 'headers' => array(), 'body' => \Lang::get('v1::response.done'));
         return \Utility::format_response(200, $response);
     }
     // Check for responses.
     $call_queue = \V1\Socket::forge()->get_results();
     // If we have queued responses, and possibly place holders, then we'll loop.
     if (!empty($call_queue) && !empty($response)) {
         // Let's loop. :)
         foreach ($call_queue as $api_name => $call_number_data) {
             /*
              * Somehow we don't need the response. Odd... I don't know why they bother giving me this stuff
              * if they don't want me to use it.
              */
             if (empty($response[$api_name])) {
                 continue;
             }
             // Find the next queued placeholder.
             $queued_placeholder = array_search(\V1\Constant::QUEUED_CALL, $response[$api_name]);
             // If we have a placeholder, then put it's value in place.
             if ($queued_placeholder !== false) {
                 $response[$api_name][$queued_placeholder] = $call_number_data[(int) key($call_number_data)];
             }
         }
     }
     // If we have template data to display, then format that now.
     if (!empty($template_data)) {
         // We only want the template we just compiled, so return that.
         if (\Session::get('response_format') === 'html') {
             return static::process_template($template_data, $response, true);
         }
         // Set the template to the array of template data.
         $response['template'] = static::process_template($template_data, $response);
         return \Utility::format_response(200, $response);
     } else {
         // No template data, so just return the responses.
         return \Utility::format_response(200, $response);
     }
 }
 public function test_set()
 {
     \V1\APIRequest::set('test4', 'find me');
     $this->assertSame('find me', \V1\APIRequest::get('test4'));
 }
 /**
  * Authenticate to the desired account.
  * 
  * @return boolean True if the authentication was successful, false otherwise.
  */
 public static function authenticate()
 {
     $auth = \Input::headers('X-Authorization');
     // If we're trying to run a JS call...
     if (empty($auth) && \V1\APIRequest::get('consumer_key', null) !== null) {
         // We're making a public call through JS, so we'll mark it as a reduced functionality call.
         \Session::set('public', true);
         // This session variable aids in logging and API functionality later.
         \Session::set('consumer_key', \V1\APIRequest::get('consumer_key'));
         $account_data = \V1\Model\Account::get_account();
         // If the account is invalid, fail.
         if (empty($account_data)) {
             return false;
         }
         /*
          * If the account holder wishes to allow for JS based calls, we'll allow safe calls to run
          * with their API key by turning on public mode.
          */
         if ($account_data['js_calls_allowed'] === 0) {
             return false;
         }
         /**
          * @TODO JS calls go through the client's IP, so we can't use a whitelist.
          * In the future, perhaps a blacklist deadicated to client IPs is in order?
          * If the account holder uses a whitelist, then they've just disabled their
          * blacklist of the client IPs. It really should be separated, but for now
          * it's unimplemented. 
          */
         // IP ACL
         if ($account_data['acl_type'] === 0 && static::ip_acl_check() === false) {
             return false;
         }
         // We're clear for lift off.
         return true;
     } elseif (!empty($auth)) {
         // Give the call full account access if we succeed in validating the request.
         \Session::set('public', false);
         // Is it an OAuth authorization header?
         if (\Str::sub($auth, 0, 5) !== 'OAuth') {
             return false;
         }
         // Parse the OAuth header into an array
         parse_str(\Str::sub($auth, 6, strlen($auth)), $tokens);
         $required_keys = array('oauth_signature', 'oauth_nonce', 'oauth_timestamp', 'oauth_consumer_key');
         // This session variable aids in logging and API functionality later.
         if (empty($tokens['oauth_consumer_key'])) {
             return false;
         }
         \Session::set('consumer_key', $tokens['oauth_consumer_key']);
         // IP ACL
         if (static::ip_acl_check() === false) {
             return false;
         }
         // Do we have all the correct keys?
         if (count(array_intersect_key(array_flip($required_keys), $tokens)) !== count($required_keys)) {
             return false;
         }
         // Verify the data integrity of the header's components, including if the timestamp is new enough.
         if (!(isset($tokens['oauth_consumer_key'], $tokens['oauth_signature'], $tokens['oauth_nonce']) && static::valid_timestamp($tokens['oauth_timestamp']) === true)) {
             return false;
         }
         // Do we have a valid nonce?
         if (static::valid_nonce($tokens) === false) {
             return false;
         }
         // Verify that the signature matches the content.
         if (static::valid_signature($tokens) === false) {
             return false;
         }
         // If we haven't failed yet, then it's valid.
         return true;
     }
     return false;
 }
 /**
  * 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;
 }