-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: V4 POST policy #177
feat: V4 POST policy #177
Conversation
Codecov Report
@@ Coverage Diff @@
## master #177 +/- ##
=========================================
Coverage 62.43% 62.43%
Complexity 552 552
=========================================
Files 31 31
Lines 5007 5007
Branches 449 449
=========================================
Hits 3126 3126
Misses 1712 1712
Partials 169 169 Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you add documentation in this PR? That would make the code here easier to understand and review.
/** | ||
* Use a virtual hosted-style hostname, which adds the bucket into the host portion of the URI | ||
* rather than the path, e.g. 'https://mybucket.storage.googleapis.com/...'. The bucket name | ||
* will be obtained from the resource passed in. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be --> is
per Google Tech Writing Guidelines
https://developers.google.com/style/tense
} | ||
|
||
/** | ||
* Provides a service account signer to sign the policy. If not provided an attempt will be made |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will be --> is
|
||
/** | ||
* Generate a path-style URL, which places the bucket name in the path portion of the URL | ||
* instead of in the hostname, e.g 'https://storage.googleapis.com/mybucket/...'. Note that this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete "Note that"
https://developers.google.com/style/tone
} | ||
|
||
/** | ||
* Generate a path-style URL, which places the bucket name in the path portion of the URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generate --> Generates
https://developers.google.com/style/reference-verbs
/** | ||
* Use a bucket-bound hostname, which replaces the storage.googleapis.com host with the name of | ||
* a CNAME bucket, e.g. a bucket named 'gcs-subdomain.my.domain.tld', or a Google Cloud Load | ||
* Balancer which routes to a bucket you own, e.g. 'my-load-balancer-domain.tld'. Note that this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete "Note that"
|
||
boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap); | ||
|
||
String url = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The ternary operator is hard to read when broken across multiple lines. Consider if else.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elliotte's comment here makes sense. Probably want to use an if-else in this case.
@@ -0,0 +1,202 @@ | |||
/* | |||
* Copyright 2019 Google LLC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2020
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be 2020.
|
||
package com.google.cloud.storage; | ||
|
||
import static com.google.cloud.storage.Storage.V4ConditionType.MATCHES; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use non-static imports here as these are not well-known names.
private final ServiceAccountCredentials serviceAccountCredentials; | ||
|
||
/** | ||
* @param testData The serialized test data representing the test case. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: no cap and no period since it's not a complete sentence
} | ||
|
||
/** | ||
* Load all of the tests and return a {@code Collection<Object[]>} representing the set of tests. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Load --> Loads
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still reviewing your PR. One ask is to add examples of how your code is expected to be used into Surface Review Doc
} | ||
} | ||
|
||
class V4PostConditions { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 could you split this up into separate files for maintainability as well?
@@ -104,14 +105,14 @@ public void test() { | |||
|
|||
SignUrlOption style = SignUrlOption.withPathStyle(); | |||
|
|||
if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.VIRTUAL_HOSTED_STYLE)) { | |||
if (testData.getUrlStyle().equals(UrlStyle.VIRTUAL_HOSTED_STYLE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you split out this into a separte PR if possible?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this specifically, I mean to split out conformance test changes into a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks again for your patience @JesseLovelace.
@@ -104,14 +105,14 @@ public void test() { | |||
|
|||
SignUrlOption style = SignUrlOption.withPathStyle(); | |||
|
|||
if (testData.getUrlStyle().equals(SigningV4Test.UrlStyle.VIRTUAL_HOSTED_STYLE)) { | |||
if (testData.getUrlStyle().equals(UrlStyle.VIRTUAL_HOSTED_STYLE)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For this specifically, I mean to split out conformance test changes into a separate PR.
@@ -1255,6 +1260,447 @@ public static SignUrlOption withQueryParams(Map<String, String> queryParams) { | |||
} | |||
} | |||
|
|||
class V4PostPolicyOption implements Serializable { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you split all V4PostPolicy helper classes into a separate file, except what's needed for generateV4PresignedPostPolicy
ease of use such as V4PostPolicyOption
?
return generateV4PresignedPostPolicy( | ||
blobInfo, V4PostFields.newBuilder().build(), duration, options); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put V4 at the end of the name please.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please do this for other helpers as well that use V4 for POST Policy. From meeting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JesseLovelace adding name change request for top level method name.
@@ -2532,6 +2978,13 @@ Blob create( | |||
*/ | |||
URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options); | |||
|
|||
V4PostPolicy generateV4PresignedPostPolicy( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using consistent naming across languages, please use: generateSignedPostPolicyV4
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some surface changes requested. Open to discussing them as well.
* @param duration how long until the form expires, in milliseconds | ||
* @param options optional post policy options | ||
*/ | ||
PostPolicyV4 generatePresignedPostPolicyV4( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
use generateSignedPostPolicyV4
*/ | ||
PostPolicyV4 generatePresignedPostPolicyV4( | ||
BlobInfo blobInfo, | ||
PostFieldsV4 fields, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fields and conditions should be optional. I want to say these should be under options.
this.value = value; | ||
} | ||
|
||
public static ConditionV4 newV4Condition(ConditionV4Type type, String element, String value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't translate correctly for ContentRangeLengths because element and value are start and end respectively.
} | ||
|
||
public static ConditionV4 newV4Condition(ConditionV4Type type, String element, String value) { | ||
return new ConditionV4(type, element, value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given PostConditionV4 requires users to use a related method. Should this class have a private static method helper as well? Also not sure how this is different from private ConditionV4()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Small nits overall LGTM.
Please also add integration tests.
/** | ||
* Presigned V4 post policy. | ||
* | ||
* @see <a href="https://cloud.google.com/storage/docs/xml-api/post-object" POST Object</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing >
/** | ||
* Class representing which fields to specify in a V4 POST request. | ||
* | ||
* @see <a href=https://cloud.google.com/storage/docs/xml-api/post-object#form_fields> POST Object Form fields</a> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No double quote "
around the URL.
return this; | ||
} | ||
|
||
public Builder addCustomCondition(ConditionV4Type type, String field, String value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suggest removing this until we get a feature request for this method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually use it in the generate method, but I can change it to package private
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
package private sgtm, thanks for clarifying!
* PostFieldsV4 fields = PostFieldsV4.newBuilder().setAcl("public-read").build(); | ||
* PostConditionsV4 conditions = PostConditionsV4.newBuilder().addContentTypeCondition(ConditionV4Type.MATCHES, "image/jpeg").build(); | ||
* | ||
* PostPolicyV4 policy = storage.generatePresignedPostPolicyV4( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
uses old, instead of generateSignedPostPolicyV4.
* | ||
* PostPolicyV4 policy = storage.generatePresignedPostPolicyV4( | ||
* BlobInfo.newBuilder("my-bucket", "my-object").build(), | ||
* fields, conditions, 7, TimeUnit.DAYS); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add an example of how to send a POST request with the policy result?
|
||
boolean usePathStyle = shouldUsePathStyleForSignedUrl(optionMap); | ||
|
||
String url = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elliotte's comment here makes sense. Probably want to use an if-else in this case.
@@ -0,0 +1,202 @@ | |||
/* | |||
* Copyright 2019 Google LLC |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should be 2020.
PostPolicyV4 generateSignedPostPolicyV4( | ||
BlobInfo blobInfo, long duration, TimeUnit unit, PostConditionsV4 conditions, PostPolicyV4Option... options); | ||
|
||
PostPolicyV4 generateSignedPostPolicyV4( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add integration tests.
public Builder setSuccessActionStatus(int successActionStatus) { | ||
this.successActionStatus = "" + successActionStatus; | ||
return this; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Custom field setter should be available here.
return this; | ||
} | ||
|
||
public Builder addCustomMetadataCondition(ConditionV4Type type, String field, String value) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be in fields instead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @JesseLovelace, added a few nit comments. Mainly the integration test should send the request to GCS and make sure it's successful.
* for(Map.Entry<String, String> entry : policy.getFields().entrySet()) { | ||
* request.addHeader(entry.getKey(), entry.getValue()); | ||
* } | ||
* client.execute(request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This example is missing the file to upload*
@@ -67,6 +67,8 @@ | |||
import com.google.cloud.storage.CopyWriter; | |||
import com.google.cloud.storage.HmacKey; | |||
import com.google.cloud.storage.HttpMethod; | |||
import com.google.cloud.storage.PostPolicyV4; | |||
import com.google.cloud.storage.PostPolicyV4.*; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Explicitly import the needed classes and not use *
.
@@ -3208,4 +3210,72 @@ public void testBucketLogging() throws ExecutionException, InterruptedException | |||
RemoteStorageHelper.forceDelete(storage, loggingBucket, 5, TimeUnit.SECONDS); | |||
} | |||
} | |||
|
|||
@Test | |||
public void testSignedPostPolicyV4() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is more of a unit test so I would say it's good to keep. I'd like to see a request similar to what you added into your documentation above:
PostFieldsV4 fields = PostFieldsV4.newBuilder().setAcl("public-read").build();
PostConditionsV4 conditions = PostConditionsV4.newBuilder().addContentTypeCondition(ConditionV4Type.MATCHES, "image/jpeg").build();
PostPolicyV4 policy = storage.generateSignedPostPolicyV4(
BlobInfo.newBuilder("my-bucket", "my-object").build(),
7, TimeUnit.DAYS, fields, conditions);
HttpClient client = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(policy.getUrl());
for(Map.Entry<String, String> entry : policy.getFields().entrySet()) {
request.addHeader(entry.getKey(), entry.getValue());
}
// Missing data to upload in request
client.execute(request);
@@ -2532,6 +2634,88 @@ Blob create( | |||
*/ | |||
URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options); | |||
|
|||
/** | |||
* Generates a URL and a map of fields that can be specified in a form to submit a POST request. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in an HTML form
You can link to relevant documentation here: https://cloud.google.com/storage/docs/xml-api/post-object#usage_and_examples
return this; | ||
} | ||
|
||
public Builder addBucketCondition(ConditionV4Type type, String bucket) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove this condition.
<dependency> | ||
<groupId>com.google.code.gson</groupId> | ||
<artifactId>gson</artifactId> | ||
<version>2.8.6</version> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's the scope for gson artifact?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, let's get it merge after you fix the Binary Compat issue.
@@ -56,7 +57,6 @@ | |||
* | |||
* @see <a href="https://cloud.google.com/storage/docs">Google Cloud Storage</a> | |||
*/ | |||
@InternalExtensionOnly | |||
public interface Storage extends Service<StorageOptions> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
InternalExtensionOnly is necessary, otherwise this change will break API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely missed this during review, thank you @dmitry-fa!
* @return the updated blob | ||
* @throws StorageException upon failure | ||
* @see <a | ||
* href="https://cloud.google.com/storage/docs/json_api/v1/objects/update">https://cloud.google.com/storage/docs/json_api/v1/objects/update</a> | ||
*/ | ||
Blob update(BlobInfo blobInfo); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed issues. Thanks @dmitry-fa
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing my notes!
Adds V4 post policy. Will add documentation once implementation is approved
Fixes: #280 #88