Authenticates a connection to Amazon S3. Do not use directly unless implementing custom methods for this class.
Access
public
Parameters
Parameter |
Type |
Required |
Description |
---|---|---|---|
|
Required |
The name of the bucket to operate on (S3 Only). |
|
|
Required |
An associative array of parameters for authenticating. See inline comments for allowed keys. |
Returns
Type |
Description |
---|---|
A |
See Also
Source
Method defined in services/s3.class.php | Toggle source view (491 lines) | View on GitHub
public function authenticate($operation, $payload) { /* * Overriding or extending this class? You can pass the following "magic" keys into $opt. * * ## verb, resource, sub_resource and query_string ## * <verb> /<resource>?<sub_resource>&<query_string> * GET /filename.txt?versions&prefix=abc&max-items=1 * * ## versionId, uploadId, partNumber, response-* ## * These don't follow the same rules as above, in that the they needs to be signed, while * other query_string values do not. * * ## curlopts ## * These values get passed directly to the cURL methods in RequestCore. * * ## fileUpload, fileDownload, seekTo ## * These are slightly modified and then passed to the cURL methods in RequestCore. * * ## headers ## * $opt['headers'] is an array, whose keys are HTTP headers to be sent. * * ## body ## * This is the request body that is sent to the server via PUT/POST. * * ## preauth ## * This is a hook that tells authenticate() to generate a pre-authenticated URL. * * ## returnCurlHandle ## * Tells authenticate() to return the cURL handle for the request instead of executing it. */ // Rename variables (to overcome inheritence issues) $bucket = $operation; $opt = $payload; // Validate the S3 bucket name if (!$this->validate_bucketname_support($bucket)) { // @codeCoverageIgnoreStart throw new S3_Exception('S3 does not support "' . $bucket . '" as a valid bucket name. Review "Bucket Restrictions and Limitations" in the S3 Developer Guide for more information.'); // @codeCoverageIgnoreEnd } // Die if $opt isn't set. if (!$opt) return false; $method_arguments = func_get_args(); // Use the caching flow to determine if we need to do a round-trip to the server. if ($this->use_cache_flow) { // Generate an identifier specific to this particular set of arguments. $cache_id = $this->key . '_' . get_class($this) . '_' . $bucket . '_' . sha1(serialize($method_arguments)); // Instantiate the appropriate caching object. $this->cache_object = new $this->cache_class($cache_id, $this->cache_location, $this->cache_expires, $this->cache_compress); if ($this->delete_cache) { $this->use_cache_flow = false; $this->delete_cache = false; return $this->cache_object->delete(); } // Invoke the cache callback function to determine whether to pull data from the cache or make a fresh request. $data = $this->cache_object->response_manager(array($this, 'cache_callback'), $method_arguments); if ($this->parse_the_response) { // Parse the XML body $data = $this->parse_callback($data); } // End! return $data; } // If we haven't already set a resource prefix and the bucket name isn't DNS-valid... if ((!$this->resource_prefix && !$this->validate_bucketname_create($bucket)) || $this->path_style) { // Fall back to the older path-style URI $this->set_resource_prefix('/' . $bucket); $this->temporary_prefix = true; } // If the bucket name has periods and we are using SSL, we need to switch to path style URLs $bucket_name_may_cause_ssl_wildcard_failures = false; if ($this->use_ssl && strpos($bucket, '.') !== false) { $bucket_name_may_cause_ssl_wildcard_failures = true; } // Determine hostname $scheme = $this->use_ssl ? 'https://' : 'http://'; if ($bucket_name_may_cause_ssl_wildcard_failures || $this->resource_prefix || $this->path_style) { // Use bucket-in-path method $hostname = $this->hostname . $this->resource_prefix . (($bucket === '' || $this->resource_prefix === '/' . $bucket) ? '' : ('/' . $bucket)); } else { $hostname = $this->vhost ? $this->vhost : (($bucket === '') ? $this->hostname : ($bucket . '.') . $this->hostname); } // Get the UTC timestamp in RFC 2616 format $date = gmdate(CFUtilities::DATE_FORMAT_RFC2616, time()); // Storage for request parameters. $resource = ''; $sub_resource = ''; $querystringparams = array(); $signable_querystringparams = array(); $string_to_sign = ''; $headers = array( 'Content-MD5' => '', 'Content-Type' => 'application/x-www-form-urlencoded', 'Date' => $date ); /*%******************************************************************************************%*/ // Do we have an authentication token? if ($this->auth_token) { $headers['X-Amz-Security-Token'] = $this->auth_token; } // Handle specific resources if (isset($opt['resource'])) { $resource .= $opt['resource']; } // Merge query string values if (isset($opt['query_string'])) { $querystringparams = array_merge($querystringparams, $opt['query_string']); } $query_string = $this->util->to_query_string($querystringparams); // Merge the signable query string values. Must be alphabetical. $signable_list = array( 'partNumber', 'response-cache-control', 'response-content-disposition', 'response-content-encoding', 'response-content-language', 'response-content-type', 'response-expires', 'uploadId', 'versionId' ); foreach ($signable_list as $item) { if (isset($opt[$item])) { $signable_querystringparams[$item] = $opt[$item]; } } $signable_query_string = $this->util->to_query_string($signable_querystringparams); // Merge the HTTP headers if (isset($opt['headers'])) { $headers = array_merge($headers, $opt['headers']); } // Compile the URI to request $conjunction = '?'; $signable_resource = '/' . str_replace('%2F', '/', rawurlencode($resource)); $non_signable_resource = ''; if (isset($opt['sub_resource'])) { $signable_resource .= $conjunction . rawurlencode($opt['sub_resource']); $conjunction = '&'; } if ($signable_query_string !== '') { $signable_query_string = $conjunction . $signable_query_string; $conjunction = '&'; } if ($query_string !== '') { $non_signable_resource .= $conjunction . $query_string; $conjunction = '&'; } if (substr($hostname, -1) === substr($signable_resource, 0, 1)) { $signable_resource = ltrim($signable_resource, '/'); } $this->request_url = $scheme . $hostname . $signable_resource . $signable_query_string . $non_signable_resource; if (isset($opt['location'])) { $this->request_url = $opt['location']; } // Gather information to pass along to other classes. $helpers = array( 'utilities' => $this->utilities_class, 'request' => $this->request_class, 'response' => $this->response_class, ); // Instantiate the request class $request = new $this->request_class($this->request_url, $this->proxy, $helpers, $this->credentials); // Update RequestCore settings $request->request_class = $this->request_class; $request->response_class = $this->response_class; $request->ssl_verification = $this->ssl_verification; // Pass along registered stream callbacks if ($this->registered_streaming_read_callback) { $request->register_streaming_read_callback($this->registered_streaming_read_callback); } if ($this->registered_streaming_write_callback) { $request->register_streaming_write_callback($this->registered_streaming_write_callback); } // Streaming uploads if (isset($opt['fileUpload'])) { if (is_resource($opt['fileUpload'])) { // Determine the length to read from the stream $length = null; // From current position until EOF by default, size determined by set_read_stream() if (isset($headers['Content-Length'])) { $length = $headers['Content-Length']; } elseif (isset($opt['seekTo'])) { // Read from seekTo until EOF by default $stats = fstat($opt['fileUpload']); if ($stats && $stats['size'] >= 0) { $length = $stats['size'] - (integer) $opt['seekTo']; } } $request->set_read_stream($opt['fileUpload'], $length); if ($headers['Content-Type'] === 'application/x-www-form-urlencoded') { $headers['Content-Type'] = 'application/octet-stream'; } } else { $request->set_read_file($opt['fileUpload']); // Determine the length to read from the file $length = $request->read_stream_size; // The file size by default if (isset($headers['Content-Length'])) { $length = $headers['Content-Length']; } elseif (isset($opt['seekTo']) && isset($length)) { // Read from seekTo until EOF by default $length -= (integer) $opt['seekTo']; } $request->set_read_stream_size($length); // Attempt to guess the correct mime-type if ($headers['Content-Type'] === 'application/x-www-form-urlencoded') { $extension = explode('.', $opt['fileUpload']); $extension = array_pop($extension); $mime_type = CFMimeTypes::get_mimetype($extension); $headers['Content-Type'] = $mime_type; } } $headers['Content-Length'] = $request->read_stream_size; $headers['Content-MD5'] = ''; } // Handle streaming file offsets if (isset($opt['seekTo'])) { // Pass the seek position to RequestCore $request->set_seek_position((integer) $opt['seekTo']); } // Streaming downloads if (isset($opt['fileDownload'])) { if (is_resource($opt['fileDownload'])) { $request->set_write_stream($opt['fileDownload']); } else { $request->set_write_file($opt['fileDownload']); } } $curlopts = array(); // Set custom CURLOPT settings if (isset($opt['curlopts'])) { $curlopts = $opt['curlopts']; } // Debug mode if ($this->debug_mode) { $curlopts[CURLOPT_VERBOSE] = true; } // Set the curl options. if (count($curlopts)) { $request->set_curlopts($curlopts); } // Do we have a verb? if (isset($opt['verb'])) { $request->set_method($opt['verb']); $string_to_sign .= $opt['verb'] . "\n"; } // Add headers and content when we have a body if (isset($opt['body'])) { $request->set_body($opt['body']); $headers['Content-Length'] = strlen($opt['body']); if ($headers['Content-Type'] === 'application/x-www-form-urlencoded') { $headers['Content-Type'] = 'application/octet-stream'; } if (!isset($opt['NoContentMD5']) || $opt['NoContentMD5'] !== true) { $headers['Content-MD5'] = $this->util->hex_to_base64(md5($opt['body'])); } } // Handle query-string authentication if (isset($opt['preauth']) && (integer) $opt['preauth'] > 0) { unset($headers['Date']); $headers['Content-Type'] = ''; $headers['Expires'] = is_int($opt['preauth']) ? $opt['preauth'] : strtotime($opt['preauth']); } // Sort headers uksort($headers, 'strnatcasecmp'); // Add headers to request and compute the string to sign foreach ($headers as $header_key => $header_value) { // Strip linebreaks from header values as they're illegal and can allow for security issues $header_value = str_replace(array("\r", "\n"), '', $header_value); // Add the header if it has a value if ($header_value !== '') { $request->add_header($header_key, $header_value); } // Generate the string to sign if ( strtolower($header_key) === 'content-md5' || strtolower($header_key) === 'content-type' || strtolower($header_key) === 'date' || (strtolower($header_key) === 'expires' && isset($opt['preauth']) && (integer) $opt['preauth'] > 0) ) { $string_to_sign .= $header_value . "\n"; } elseif (substr(strtolower($header_key), 0, 6) === 'x-amz-') { $string_to_sign .= strtolower($header_key) . ':' . $header_value . "\n"; } } // Add the signable resource location $string_to_sign .= ($this->resource_prefix ? $this->resource_prefix : ''); $string_to_sign .= (($bucket === '' || $this->resource_prefix === '/' . $bucket) ? '' : ('/' . $bucket)) . $signable_resource . urldecode($signable_query_string); // Hash the AWS secret key and generate a signature for the request. $signature = base64_encode(hash_hmac('sha1', $string_to_sign, $this->secret_key, true)); $request->add_header('Authorization', 'AWS ' . $this->key . ':' . $signature); // If we're generating a URL, return the URL to the calling method. if (isset($opt['preauth']) && (integer) $opt['preauth'] > 0) { $query_params = array( 'AWSAccessKeyId' => $this->key, 'Expires' => $headers['Expires'], 'Signature' => $signature, ); // If using short-term credentials, add the token to the query string if ($this->auth_token) { $query_params['x-amz-security-token'] = $this->auth_token; } return $this->request_url . $conjunction . http_build_query($query_params, '', '&'); } elseif (isset($opt['preauth'])) { return $this->request_url; } /*%******************************************************************************************%*/ // If our changes were temporary, reset them. if ($this->temporary_prefix) { $this->temporary_prefix = false; $this->resource_prefix = null; } // Manage the (newer) batch request API or the (older) returnCurlHandle setting. if ($this->use_batch_flow) { $handle = $request->prep_request(); $this->batch_object->add($handle); $this->use_batch_flow = false; return $handle; } elseif (isset($opt['returnCurlHandle']) && $opt['returnCurlHandle'] === true) { return $request->prep_request(); } // Send! $request->send_request(); // Prepare the response $headers = $request->get_response_header(); $headers['x-aws-request-url'] = $this->request_url; $headers['x-aws-redirects'] = $this->redirects; $headers['x-aws-stringtosign'] = $string_to_sign; $headers['x-aws-requestheaders'] = $request->request_headers; // Did we have a request body? if (isset($opt['body'])) { $headers['x-aws-requestbody'] = $opt['body']; } $data = new $this->response_class($headers, $this->parse_callback($request->get_response_body()), $request->get_response_code()); // Did Amazon tell us to redirect? Typically happens for multiple rapid requests EU datacenters. // @see: http://docs.amazonwebservices.com/AmazonS3/latest/dev/Redirects.html // @codeCoverageIgnoreStart if ((integer) $request->get_response_code() === 307) // Temporary redirect to new endpoint. { $this->redirects++; $opt['location'] = $headers['location']; $data = $this->authenticate($bucket, $opt); } // Was it Amazon's fault the request failed? Retry the request until we reach $max_retries. elseif ((integer) $request->get_response_code() === 500 || (integer) $request->get_response_code() === 503) { if ($this->redirects <= $this->max_retries) { // Exponential backoff $delay = (integer) (pow(4, $this->redirects) * 100000); usleep($delay); $this->redirects++; $data = $this->authenticate($bucket, $opt); } } // @codeCoverageIgnoreEnd // Return! $this->redirects = 0; return $data; }