diff --git a/storage/README.md b/storage/README.md index aaa3056880..1df2859769 100644 --- a/storage/README.md +++ b/storage/README.md @@ -32,16 +32,19 @@ This simple command-line application demonstrates how to invoke Google Cloud Sto 5. Run `php storage.php`. The following commands are available: ```sh - bucket-acl Manage the ACL for Cloud Storage buckets. - bucket-default-acl Manage the default ACL for Cloud Storage buckets. - bucket-labels Manage Cloud Storage bucket labels - bucket-lock Manage Cloud Storage retention policies and holds - buckets Manage Cloud Storage buckets - encryption Upload and download Cloud Storage objects with encryption - object-acl Manage the ACL for Cloud Storage objects - objects Manage Cloud Storage objects - requester-pays Manage Cloud Storage requester pays buckets and objects - bucket-policy-only Manage Cloud Storage bucket policy only buckets + bucket-acl Manage the ACL for Cloud Storage buckets. + bucket-default-acl Manage the default ACL for Cloud Storage buckets. + bucket-labels Manage Cloud Storage bucket labels + bucket-lock Manage Cloud Storage retention policies and holds + buckets Manage Cloud Storage buckets + encryption Upload and download Cloud Storage objects with encryption + object-acl Manage the ACL for Cloud Storage objects + objects Manage Cloud Storage objects + requester-pays Manage Cloud Storage requester pays buckets and objects + bucket-policy-only Manage Cloud Storage bucket policy only buckets + get-object-v2-signed-url Generate a v2 signed URL for downloading an object. + get-object-v4-signed-url Generate a v4 signed URL for downloading an object. + get-object-v4-upload-signed-url Generate a v4 signed URL for uploading an object. ``` 6. Run `php storage.php COMMAND --help` to print information about the usage of each command. diff --git a/storage/composer.json b/storage/composer.json index 4e7f1e3006..75e2505a94 100644 --- a/storage/composer.json +++ b/storage/composer.json @@ -38,6 +38,8 @@ "src/get_object_acl.php", "src/get_object_acl_for_entity.php", "src/get_object_v2_signed_url.php", + "src/get_object_v4_signed_url.php", + "src/get_object_v4_upload_signed_url.php", "src/get_requester_pays_status.php", "src/get_retention_policy.php", "src/get_default_event_based_hold.php", @@ -65,6 +67,7 @@ }, "require-dev": { "phpunit/phpunit": "^5", - "google/cloud-tools": "^0.8.5" + "google/cloud-tools": "^0.8.5", + "guzzlehttp/guzzle": "^6.3" } } diff --git a/storage/src/get_object_v4_signed_url.php b/storage/src/get_object_v4_signed_url.php new file mode 100644 index 0000000000..665c05784a --- /dev/null +++ b/storage/src/get_object_v4_signed_url.php @@ -0,0 +1,55 @@ +bucket($bucketName); + $object = $bucket->object($objectName); + $url = $object->signedUrl( + # This URL is valid for 15 minutes + new \DateTime('15 min'), + [ + 'version' => 'v4', + ] + ); + + print('Generated GET signed URL:' . PHP_EOL); + print($url . PHP_EOL); + print('You can use this URL with any user agent, for example:' . PHP_EOL); + print('curl ' . $url . PHP_EOL); +} +# [END storage_generate_signed_url_v4] diff --git a/storage/src/get_object_v4_upload_signed_url.php b/storage/src/get_object_v4_upload_signed_url.php new file mode 100644 index 0000000000..20f646efdb --- /dev/null +++ b/storage/src/get_object_v4_upload_signed_url.php @@ -0,0 +1,58 @@ +bucket($bucketName); + $object = $bucket->object($objectName); + $url = $object->signedUrl( + # This URL is valid for 15 minutes + new \DateTime('15 min'), + [ + 'method' => 'PUT', + 'contentType' => 'application/octet-stream', + 'version' => 'v4', + ] + ); + + print('Generated PUT signed URL:' . PHP_EOL); + print($url . PHP_EOL); + print('You can use this URL with any user agent, for example:' . PHP_EOL); + print("curl -X PUT -H 'Content-Type: application/octet-stream' " . + '--upload-file my-file ' . $url . PHP_EOL); +} +# [END storage_generate_upload_signed_url_v4] diff --git a/storage/storage.php b/storage/storage.php index b9b7af67a8..aace003a63 100644 --- a/storage/storage.php +++ b/storage/storage.php @@ -487,6 +487,40 @@ get_object_v2_signed_url(https://github.com/$bucketName,%20$objectName); }); +$application->add(new Command('get-object-v4-signed-url')) + ->setDescription('Generate a v4 signed URL for downloading an object.') + ->setHelp(<<%command.name% command generates a v4 signed URL for downloading an object. + +php %command.full_name% --help + +EOF + ) + ->addArgument('bucket', InputArgument::REQUIRED, 'The Cloud Storage bucket name') + ->addArgument('object', InputArgument::REQUIRED, 'The Cloud Storage object name') + ->setCode(function ($input, $output) { + $bucketName = $input->getArgument('bucket'); + $objectName = $input->getArgument('object'); + get_object_v4_signed_url(https://github.com/$bucketName,%20$objectName); + }); + +$application->add(new Command('get-object-v4-upload-signed-url')) + ->setDescription('Generate a v4 signed URL for uploading an object.') + ->setHelp(<<%command.name% command generates a v4 signed URL for uploading an object. + +php %command.full_name% --help + +EOF + ) + ->addArgument('bucket', InputArgument::REQUIRED, 'The Cloud Storage bucket name') + ->addArgument('object', InputArgument::REQUIRED, 'The Cloud Storage object name') + ->setCode(function ($input, $output) { + $bucketName = $input->getArgument('bucket'); + $objectName = $input->getArgument('object'); + get_object_v4_upload_signed_url(https://github.com/$bucketName,%20$objectName); + }); + // for testing if (getenv('PHPUNIT_TESTS') === '1') { return $application; diff --git a/storage/test/ObjectSignedUrlTest.php b/storage/test/ObjectSignedUrlTest.php index cd957be1ff..ca026495ed 100644 --- a/storage/test/ObjectSignedUrlTest.php +++ b/storage/test/ObjectSignedUrlTest.php @@ -20,6 +20,7 @@ use Google\Cloud\TestUtils\TestTrait; use Google\Cloud\TestUtils\ExecuteCommandTrait; use Google\Cloud\Storage\StorageClient; +use GuzzleHttp\Client; use PHPUnit\Framework\TestCase; /** @@ -30,6 +31,7 @@ class ObjectSignedUrlTest extends TestCase use TestTrait; use ExecuteCommandTrait; + private static $storage; private static $bucketName; private static $objectName; private static $commandFile = __DIR__ . '/../storage.php'; @@ -37,10 +39,11 @@ class ObjectSignedUrlTest extends TestCase /** @beforeClass */ public static function setUpObject() { - $storage = new StorageClient(); + self::$storage = new StorageClient(); self::$bucketName = self::requireEnv('GOOGLE_STORAGE_BUCKET'); self::$objectName = sprintf('test-object-%s', time()); - $storage + // Pre-upload an object for testing GET signed urls + self::$storage ->bucket(self::$bucketName) ->upload("test file content", [ 'name' => self::$objectName @@ -56,4 +59,48 @@ public function testGetV2SignedUrl() $this->assertContains("The signed url for " . self::$objectName . " is", $output); } + + public function testGetV4SignedUrl() + { + $output = $this->runCommand('get-object-v4-signed-url', [ + 'bucket' => self::$bucketName, + 'object' => self::$objectName, + ]); + + $this->assertContains('Generated GET signed URL:', $output); + } + + public function testGetV4UploadSignedUrl() + { + $uploadObjectName = sprintf('test-upload-object-%s', time()); + + $output = $this->runCommand('get-object-v4-upload-signed-url', [ + 'bucket' => self::$bucketName, + 'object' => $uploadObjectName, + ]); + + $this->assertContains('Generated PUT signed URL:', $output); + + // Extract the signed URL from command output. + preg_match_all('/URL:\n([^\s]+)/', $output, $matches); + $url = $matches[1][0]; + + // Make a PUT request using the signed URL. + $client = new Client(); + $res = $client->request('PUT', $url, [ + 'headers' => [ + 'Content-Type' => 'application/octet-stream', + ], + 'body' => 'upload content' + ]); + + $this->assertEquals(200, $res->getStatusCode()); + + // Assert file is correctly uploaded to the bucket. + $content = self::$storage + ->bucket(self::$bucketName) + ->object($uploadObjectName) + ->downloadAsString(); + $this->assertEquals('upload content', $content); + } }