Skip to content
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

fix: PostPolicyV4 classes could be improved #442

Merged
merged 9 commits into from Aug 21, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -18,56 +18,123 @@

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
* Presigned V4 post policy.
* Presigned V4 post policy. Instances of {@code PostPolicyV4} include a URL and a map of fields
* that can be specified in an HTML form to submit a POST request to upload an object.
*
* @see <a href="https://cloud.google.com/storage/docs/xml-api/post-object">POST Object</a>
* <p>See <a href="https://cloud.google.com/storage/docs/xml-api/post-object">POST Object</a> for
* details of upload by using HTML forms.
*
* <p>See {@link Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit,
* PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)} for
* example of usage.
*/
public final class PostPolicyV4 {
private final String url;
private final Map<String, String> fields;

private PostPolicyV4(String url, Map<String, String> fields) {
try {
new URL(url);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What URLs do you really want to allow and not allow here? java.net.URL is based on java protocol handlers, and is likely to be either too lenient or too strict depending on what you need.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to allow strings that could be used to construct an URL object and reject strings like 'google.com' given by mistake instead of 'https://google.com'. Catching 'ftp://google.com' is not the goal.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds like new URI(url).isAbsolute() is your best bet then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree, fixed.

} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
dmitry-fa marked this conversation as resolved.
Show resolved Hide resolved
}
PostFieldsV4.validateFields(fields);

this.url = url;
this.fields = fields;
this.fields = Collections.unmodifiableMap(fields);
dmitry-fa marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Constructs {@code PostPolicyV4} instance of the given URL and fields map.
*
* @param url URL for the HTTP POST request
* @param fields HTML form fields
* @return constructed object
* @throws IllegalArgumentException if URL is malformed or fields are not valid
*/
public static PostPolicyV4 of(String url, Map<String, String> fields) {
return new PostPolicyV4(url, fields);
}

/** Returns the URL for the HTTP POST request */
public String getUrl() {
return url;
}

/** Returns the HTML form fields */
public Map<String, String> getFields() {
return fields;
}

/**
* Class representing which fields to specify in a V4 POST request.
* A helper class to define fields to be specified in a V4 POST request. Instance of this class
* helps to construct {@code PostPolicyV4} objects. Used in: {@link
* Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
* PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see <a href="https://cloud.google.com/storage/docs/xml-api/post-object#form_fields">POST
* Object Form fields</a>
*/
public static final class PostFieldsV4 {
private final Map<String, String> fieldsMap;
private static final List<String> VALID_FIELDS =
Arrays.asList(
"acl",
"bucket",
"cache-control",
"content-disposition",
"content-encoding",
"content-type",
"expires",
"file",
"key",
"policy",
"success_action_redirect",
"success_action_status",
"x-goog-algorithm",
"x-goog-credential",
"x-goog-date",
"x-goog-signature");

private static void validateFields(Map<String, String> fields) {
for (String key : fields.keySet()) {
if (!VALID_FIELDS.contains(key.toLowerCase())
&& !key.startsWith(Builder.CUSTOM_FIELD_PREFIX)) {
throw new IllegalArgumentException("Invalid key: " + key);
dmitry-fa marked this conversation as resolved.
Show resolved Hide resolved
}
}
}

private PostFieldsV4(Builder builder) {
this.fieldsMap = builder.fieldsMap;
this(builder.fieldsMap);
}

private PostFieldsV4(Map<String, String> fields) {
this.fieldsMap = fields;
validateFields(fields);
this.fieldsMap = Collections.unmodifiableMap(fields);
}

/**
* Constructs {@code PostPolicyV4.PostFieldsV4} object of the given field map.
*
* @param fields a map of the HTML form fields
* @return constructed object
* @throws IllegalArgumentException if an unsupported field is specified
*/
public static PostFieldsV4 of(Map<String, String> fields) {
return new PostFieldsV4(fields);
}
Expand Down Expand Up @@ -112,8 +179,14 @@ public Builder setContentEncoding(String contentEncoding) {
return this;
}

/**
* @deprecated Invocation of this method has no effect, because all valid HTML form fields
* except Content-Length can use exact matching. Use {@link
* PostPolicyV4.PostConditionsV4.Builder#addContentLengthRangeCondition(int, int)} to
* specify a range for the content-length.
*/
@Deprecated
public Builder setContentLength(int contentLength) {
fieldsMap.put("content-length", "" + contentLength);
return this;
}

Expand All @@ -122,7 +195,7 @@ public Builder setContentType(String contentType) {
return this;
}

/** @deprecated use {@link #setExpires(String)} */
/** @deprecated Use {@link #setExpires(String)}. */
@Deprecated
public Builder Expires(String expires) {
return setExpires(expires);
Expand All @@ -143,7 +216,7 @@ public Builder setSuccessActionStatus(int successActionStatus) {
return this;
}

/** @deprecated use {@link #setCustomMetadataField(String, String)} */
/** @deprecated Use {@link #setCustomMetadataField(String, String)}. */
@Deprecated
public Builder AddCustomMetadataField(String field, String value) {
return setCustomMetadataField(field, value);
Expand All @@ -160,7 +233,9 @@ public Builder setCustomMetadataField(String field, String value) {
}

/**
* Class for specifying conditions in a V4 POST Policy document.
* A helper class for specifying conditions in a V4 POST Policy document. Used in: {@link
* Storage#generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
* PostPolicyV4.PostConditionsV4, Storage.PostPolicyV4Option...)}.
*
* @see <a href="https://cloud.google.com/storage/docs/authentication/signatures#policy-document">
* Policy document</a>
Expand All @@ -183,14 +258,14 @@ public static Builder newBuilder() {
}

public Set<ConditionV4> getConditions() {
return conditions;
return Collections.unmodifiableSet(conditions);
}

public static class Builder {
Set<ConditionV4> conditions;
private final Set<ConditionV4> conditions;

private Builder() {
this.conditions = new LinkedHashSet<>();
this(new LinkedHashSet<ConditionV4>());
}

private Builder(Set<ConditionV4> conditions) {
Expand Down Expand Up @@ -280,7 +355,7 @@ Builder addCustomCondition(ConditionV4Type type, String field, String value) {
}

/**
* Class for a V4 POST Policy document.
* Class for a V4 POST Policy document. Used by Storage to construct {@code PostPolicyV4} objects.
*
* @see <a href="https://cloud.google.com/storage/docs/authentication/signatures#policy-document">
* Policy document</a>
Expand Down Expand Up @@ -367,9 +442,20 @@ public String toJson() {
}

public enum ConditionV4Type {
MATCHES,
STARTS_WITH,
CONTENT_LENGTH_RANGE
MATCHES("eq"),
STARTS_WITH("starts-with"),
CONTENT_LENGTH_RANGE("content-length-range");

private final String name;

ConditionV4Type(String name) {
this.name = name;
}

@Override
public String toString() {
return name;
}
}

/**
Expand All @@ -378,10 +464,10 @@ public enum ConditionV4Type {
* @see <a href="https://cloud.google.com/storage/docs/authentication/signatures#policy-document">
* Policy document</a>
*/
static final class ConditionV4 {
final ConditionV4Type type;
final String operand1;
final String operand2;
public static final class ConditionV4 {
public final ConditionV4Type type;
public final String operand1;
public final String operand2;

private ConditionV4(ConditionV4Type type, String operand1, String operand2) {
this.type = type;
Expand All @@ -401,5 +487,18 @@ public boolean equals(Object other) {
public int hashCode() {
return Objects.hash(type, operand1, operand2);
}

/**
* Examples of returned strings: {@code ["eq", "$key", "test-object"]}, {@code ["starts-with",
* "$acl", "public"]}, {@code ["content-length-range", 246, 266]}.
*/
@Override
public String toString() {
String body =
type == ConditionV4Type.CONTENT_LENGTH_RANGE
? operand1 + ", " + operand2
: "\"$" + operand1 + "\", \"" + operand2 + "\"";
return "[\"" + type + "\", " + body + "]";
}
}
}
Expand Up @@ -2843,6 +2843,8 @@ Blob createFrom(
* }</pre>
*
* @param blobInfo the blob uploaded in the form
* @param duration time before expiration
* @param unit duration time unit
* @param fields the fields specified in the form
* @param conditions which conditions every upload must satisfy
* @param duration how long until the form expires, in milliseconds
Expand All @@ -2861,9 +2863,8 @@ PostPolicyV4 generateSignedPostPolicyV4(

/**
* Generates a presigned post policy without any conditions. Automatically creates required
* conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long
* duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions,
* PostPolicyV4Option... options) above.
* conditions. See full documentation for {@link #generateSignedPostPolicyV4(BlobInfo, long,
* TimeUnit, PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
Expand All @@ -2874,9 +2875,8 @@ PostPolicyV4 generateSignedPostPolicyV4(

/**
* Generates a presigned post policy without any fields. Automatically creates required fields.
* See full documentation for generateSignedPostPolicyV4( BlobInfo blobInfo, long duration,
* TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions, PostPolicyV4Option... options)
* above.
* See full documentation for {@link #generateSignedPostPolicyV4(BlobInfo, long, TimeUnit,
* PostPolicyV4.PostFieldsV4, PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo,
Expand All @@ -2887,9 +2887,9 @@ PostPolicyV4 generateSignedPostPolicyV4(

/**
* Generates a presigned post policy without any fields or conditions. Automatically creates
* required fields and conditions. See full documentation for generateSignedPostPolicyV4( BlobInfo
* blobInfo, long duration, TimeUnit unit, PostFieldsV4 fields, PostConditionsV4 conditions,
* PostPolicyV4Option... options) above.
* required fields and conditions. See full documentation for {@link
* #generateSignedPostPolicyV4(BlobInfo, long, TimeUnit, PostPolicyV4.PostFieldsV4,
* PostPolicyV4.PostConditionsV4, PostPolicyV4Option...)}.
*/
PostPolicyV4 generateSignedPostPolicyV4(
BlobInfo blobInfo, long duration, TimeUnit unit, PostPolicyV4Option... options);
Expand Down