/** * Run the security call and see what falls out. * * @param \Raml\SecurityScheme $securityscheme_obj The security scheme to process the call data for * @param \V1\APICall $apicall_obj The APICall object * * @return bool True on success, or false on fail */ public function run(\Raml\SecurityScheme $securityscheme_obj, \V1\APICall $apicall_obj) { /** * @link https://en.wikipedia.org/wiki/Basic_access_authentication */ $credentials = $apicall_obj->get_credentials(); // Make sure that we have the required data. if (empty($credentials['BASIC_USERNAME']) || empty($credentials['BASIC_PASSWORD'])) { return false; } $encoded_credentials = 'Basic ' . base64_encode($credentials['BASIC_USERNAME'] . ':' . $credentials['BASIC_PASSWORD']); $apicall_obj->set_header('Authorization', $encoded_credentials); return true; }
/** * 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) { $settings = $securityscheme_obj->getSettings(); $credentials = $apicall_obj->get_credentials(); // Save the credentials \V1\Keyring::set_credentials($credentials); /** * By default we'll return the response from the authentication request so that it's meaningful. * However, in doing so, we'll need to block the main request, so developers may set this flag * to ignore the authentication, signifying that they've already got the information they needed * from it. * * NOTE: This security method is meant as a basic way to catch security methods we otherwise * haven't implemented in our system. Take it for what it's worth. * * @TODO When using this security method, skip processing the APICall object for a speedup. */ if (!empty($credentials['CUSTOM_IGNORE_AUTH'])) { return true; } // Remove unused credentials so as not to replace bad variables in the template. foreach ($credentials as $variable => $entry) { if (strpos($variable, 'CUSTOM_') !== 0) { unset($credentials[$variable]); } } // We need the method or we'll fail the call. if (empty($settings['method'])) { $this->error = true; return $this; } // Normalize the data into arrays. $described_by = $securityscheme_obj->getDescribedBy(); $headers = $this->get_param_array($described_by->getHeaders()); $query_params = $this->get_param_array($described_by->getQueryParameters()); $bodies = $described_by->getBodies(); $method = \Str::upper($settings['method']); $url = $settings['url']; // Grab the body if we have one, and the method supports one. $body = null; $body_type = null; if (count($bodies) > 0 && !in_array($method, array('GET', 'HEAD'))) { reset($bodies); $body_type = key($bodies); $body = $bodies[$body_type]->getExamples()[0]; } /** * NOTE: These replacements may ruin the formatting or allow for people to inject data into them. * API Providers should be aware of that possibility. * * @TODO In the future, we can consider implementing checking to verify that people aren't sending * crap data through the system. */ $headers = $this->remove_cr_and_lf($this->replace_variables($headers, $credentials)); $query_params = $this->remove_cr_and_lf($this->replace_variables($query_params, $credentials)); $body = $this->replace_variables($body, $credentials); if (!empty($query_params)) { $query_string = http_build_query($query_params, null, '&'); if (strpos($url, '?') === false) { $url .= '?' . $query_string; } else { $url .= '&' . $query_string; } } /** * RUNCLE RICK'S RAD RUN CALLS (The second coming!) */ $curl = \Remote::forge($url, 'curl', $method); // Set the headers $headers = \V1\RunCall::get_headers($headers); foreach ($headers as $header_name => $header_value) { $curl->set_header($header_name, $header_value); } // Return the headers $curl->set_option(CURLOPT_HEADER, true); // If we need a body, set that. if (!empty($body) && !in_array($method, array('GET', 'HEAD'))) { $curl->set_header('Content-Type', $body_type); $curl->set_params($body); } // Run the request try { $response = $curl->execute()->response(); } catch (\RequestStatusException $e) { $response = \Remote::get_response($curl); } catch (\RequestException $e) { $this->error = true; return $this; } // Set the usage stats, and format the response return \V1\Socket::prepare_response(array('status' => $response->status, 'headers' => $response->headers, 'body' => $response->body)); }
/** * 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. }
/** * 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) { $settings = $securityscheme_obj->getSettings()->asArray(); $credentials = $apicall_obj->get_credentials(); $settings['authorization'] = empty($settings['authorization']) ? 'header' : \Str::lower($settings['authorization']); // Verify that we have the required credentials for the request. if (empty($credentials['OAUTH_CONSUMER_KEY']) || empty($credentials['OAUTH_CONSUMER_SECRET']) || empty($credentials['OAUTH_USER_ID'])) { $this->error = true; return $this; } // Store the proper credentials in the DB. $this->store_credentials($credentials); // Pull data from the cache for the current request, allowing for multiple authentications for the customer. $this->cache_id = hash('sha256', $credentials['OAUTH_CONSUMER_KEY'] . $credentials['OAUTH_CONSUMER_SECRET'] . $credentials['OAUTH_USER_ID']); $credentials = array_replace($this->get_cache(), $credentials); // Where should we set the authorization data? switch ($settings['authorizeLocation']) { case 'header': $authorize_location = OAUTH_AUTH_TYPE_AUTHORIZATION; break; case 'query': $authorize_location = OAUTH_AUTH_TYPE_URI; break; case 'body': $authorize_location = OAUTH_AUTH_TYPE_FORM; break; case 'none': $authorize_location = OAUTH_AUTH_TYPE_NONE; break; } try { // Create the PECL installed OAuth object. $oauth = new \OAuth($credentials['OAUTH_CONSUMER_KEY'], $credentials['OAUTH_CONSUMER_SECRET'], $settings['signatureMethod'], $authorize_location); if (\Fuel::$env !== 'production') { $oauth->enableDebug(); } if (empty($credentials['OAUTH_ACCESS_TOKEN']) || empty($credentials['OAUTH_ACCESS_TOKEN_SECRET'])) { // Get our access token and secret. if (($credentials = $this->get_access_tokens($oauth, $settings, $credentials)) === false) { $this->error = true; return $this; } // Authentication of my second leg (Yup. It's hairy, so it must be mine.) if (!empty($credentials['errors'])) { return $credentials; } } $oauth->setToken($credentials['OAUTH_ACCESS_TOKEN'], $credentials['OAUTH_ACCESS_TOKEN_SECRET']); // Collect parameters to build our signature $params = null; if ($apicall_obj->get_body_type() === 'application/x-www-form-urlencoded') { // If we need to handle string bodies later, we will. if (is_array($apicall_obj->get_method_params())) { $params = http_build_query($apicall_obj->get_method_params(), null, '&', PHP_QUERY_RFC3986) . '&'; } } $params .= http_build_query($apicall_obj->get_query_params(), null, '&') . '&' . ($params .= http_build_query($apicall_obj->get_headers(), null, '&')); $header = $oauth->getRequestHeader($apicall_obj->get_method(), $apicall_obj->get_url(), $params); $apicall_obj->set_header('Authorization', $header); return true; } catch (\OAuthException $e) { // Something went wrong, so destroy the cache so it can get fixed. $this->delete_cache(); // Let the script automatically continue searching for security methods. $this->error = true; return $this; } }