From 44af2393bdd141cd64c93629673e207d94b2525f Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sat, 9 Sep 2017 20:40:57 +0200 Subject: [PATCH 01/11] #2421 Ordered headers after sign. Otherwise signature does not match. --- .../google/cloud/storage/SignatureInfo.java | 158 ++++++++++++++++++ .../com/google/cloud/storage/Storage.java | 16 +- .../com/google/cloud/storage/StorageImpl.java | 94 +++++++---- .../cloud/storage/SignatureInfoTest.java | 54 ++++++ .../google/cloud/storage/StorageImplTest.java | 58 +++++++ 5 files changed, 345 insertions(+), 35 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java new file mode 100644 index 000000000000..df1b9dbe5673 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -0,0 +1,158 @@ +package com.google.cloud.storage; + +import static com.google.common.base.Preconditions.checkArgument; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +/** + * Signature Info holds payload components of the string that requires signing. + * + * @see https://cloud.google.com/storage/docs/access-control/signed-urls#string-components + */ +public class SignatureInfo { + + private static final char COMPONENT_SEPARATOR = '\n'; + private static final char HEADER_SEPARATOR = ':'; + + private HttpMethod httpVerb; + private String contentMd5; + private String contentType; + private long expiration; + private Map canonicalizedExtensionHeaders; + private Path canonicalizedResource; + + private SignatureInfo(Builder builder) { + this.httpVerb = builder.httpVerb; + this.contentMd5 = builder.contentMd5; + this.contentType = builder.contentType; + this.expiration = builder.expiration; + this.canonicalizedExtensionHeaders = builder.canonicalizedExtensionHeaders; + this.canonicalizedResource = builder.canonicalizedResource; + } + + /** + * Constructs payload to be signed. + * + * @return + * @see https://cloud.google.com/storage/docs/access-control#Signed-URLs + */ + public String constructUnsignedPayload() { + StringBuilder payload = new StringBuilder(); + + payload.append(httpVerb.name()).append(COMPONENT_SEPARATOR); + if (contentMd5 != null) { + payload.append(contentMd5); + } + payload.append(COMPONENT_SEPARATOR); + + if (contentType != null) { + payload.append(contentType); + } + payload.append(COMPONENT_SEPARATOR); + + payload.append(expiration).append(COMPONENT_SEPARATOR); + + if (canonicalizedExtensionHeaders != null) { + + List orderedKeys = new ArrayList<>(canonicalizedExtensionHeaders.keySet()); + Collections.sort(orderedKeys); + + for (String key : orderedKeys) { + payload.append(key).append(HEADER_SEPARATOR).append(canonicalizedExtensionHeaders.get(key)) + .append(COMPONENT_SEPARATOR); + } + } + payload.append(canonicalizedResource); + + return payload.toString(); + } + + public HttpMethod getHttpVerb() { + return httpVerb; + } + + public String getContentMd5() { + return contentMd5; + } + + public String getContentType() { + return contentType; + } + + public long getExpiration() { + return expiration; + } + + public Map getCanonicalizedExtensionHeaders() { + return canonicalizedExtensionHeaders; + } + + public Path getCanonicalizedResource() { + return canonicalizedResource; + } + + public final static class Builder { + + private HttpMethod httpVerb; + private String contentMd5; + private String contentType; + private long expiration; + private Map canonicalizedExtensionHeaders; + private Path canonicalizedResource; + + /** + * Constructs builder. + * + * @param httpVerb the HTTP method + * @param expiration the EPOX expiration date + * @param canonicalizedResource the resource path + * @throws IllegalArgumentException if required field is not provided. + */ + public Builder(HttpMethod httpVerb, long expiration, Path canonicalizedResource) { + checkArgument(httpVerb != null, "Required HTTP method"); + checkArgument(canonicalizedResource != null, "Required canonicalized resource"); + + this.httpVerb = httpVerb; + this.expiration = expiration; + this.canonicalizedResource = canonicalizedResource; + } + + public Builder(SignatureInfo signatureInfo) { + this.httpVerb = signatureInfo.httpVerb; + this.contentMd5 = signatureInfo.contentMd5; + this.contentType = signatureInfo.contentType; + this.expiration = signatureInfo.expiration; + this.canonicalizedExtensionHeaders = signatureInfo.canonicalizedExtensionHeaders; + this.canonicalizedResource = signatureInfo.canonicalizedResource; + } + + public Builder setContentMd5(String contentMd5) { + this.contentMd5 = contentMd5; + + return this; + } + + public Builder setContentType(String contentType) { + this.contentType = contentType; + + return this; + } + + public Builder setCanonicalizedExtensionHeaders( + Map canonicalizedExtensionHeaders) { + this.canonicalizedExtensionHeaders = canonicalizedExtensionHeaders; + + return this; + } + + /** + * Creates an {@code SignatureInfo} object from this builder. + */ + public SignatureInfo build() { + return new SignatureInfo(this); + } + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 5fd9fe38ae0e..21fa68c054f3 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -46,6 +46,7 @@ import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -854,7 +855,7 @@ class SignUrlOption implements Serializable { private final Object value; enum Option { - HTTP_METHOD, CONTENT_TYPE, MD5, SERVICE_ACCOUNT_CRED + HTTP_METHOD, CONTENT_TYPE, MD5, EXT_HEADERS, SERVICE_ACCOUNT_CRED } private SignUrlOption(Option option, Object value) { @@ -874,7 +875,7 @@ Object getValue() { * The HTTP method to be used with the signed URL. */ public static SignUrlOption httpMethod(HttpMethod httpMethod) { - return new SignUrlOption(Option.HTTP_METHOD, httpMethod.name()); + return new SignUrlOption(Option.HTTP_METHOD, httpMethod); } /** @@ -892,6 +893,17 @@ public static SignUrlOption withContentType() { public static SignUrlOption withMd5() { return new SignUrlOption(Option.MD5, true); } + + /** + * Use it if signature should include the blob's canonicalized extended headers. + * When used, users of the signed URL should include the canonicalized extended headers with + * their request. + * + * @see /a> + */ + public static SignUrlOption withExtHeaders(Map extHeaders) { + return new SignUrlOption(Option.EXT_HEADERS, extHeaders); + } /** * Provides a service account signer to sign the URL. If not provided an attempt will be made to diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 1a7dc91e18b9..ab0e4b480c89 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -68,6 +68,8 @@ import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -509,54 +511,80 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio "Signing key was not provided and could not be derived"); credentials = (ServiceAccountSigner) this.getOptions().getCredentials(); } - // construct signature - see https://cloud.google.com/storage/docs/access-control#Signed-URLs - StringBuilder stBuilder = new StringBuilder(); - if (optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD)) { - stBuilder.append(optionMap.get(SignUrlOption.Option.HTTP_METHOD)); - } else { - stBuilder.append(HttpMethod.GET); - } - stBuilder.append('\n'); - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { - checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); - stBuilder.append(blobInfo.getMd5()); - } - stBuilder.append('\n'); - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { - checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); - stBuilder.append(blobInfo.getContentType()); - } - stBuilder.append('\n'); + long expiration = TimeUnit.SECONDS.convert( - getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); - stBuilder.append(expiration).append('\n'); - StringBuilder path = new StringBuilder(); - if (!blobInfo.getBucket().startsWith("/")) { - path.append('/'); + getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); + + StringBuilder stPath = new StringBuilder(); + if (!blobInfo.getBucket().startsWith(PATH_DELIMITER)) { + stPath.append(PATH_DELIMITER); } - path.append(blobInfo.getBucket()); - if (!blobInfo.getBucket().endsWith("/")) { - path.append('/'); + stPath.append(blobInfo.getBucket()); + if (!blobInfo.getBucket().endsWith(PATH_DELIMITER)) { + stPath.append(PATH_DELIMITER); } - if (blobInfo.getName().startsWith("/")) { - path.setLength(path.length() - 1); + if (blobInfo.getName().startsWith(PATH_DELIMITER)) { + stPath.setLength(stPath.length() - 1); } + String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); - path.append(escapedName.replace("?", "%3F")); - stBuilder.append(path); - try { - byte[] signatureBytes = credentials.sign(stBuilder.toString().getBytes(UTF_8)); - stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); + stPath.append(escapedName.replace("?", "%3F")); + + Path path = Paths.get(stPath.toString()); + + try { + SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); + byte[] signatureBytes = + credentials.sign(signatureInfo.constructUnsignedPayload().getBytes(UTF_8)); + StringBuilder stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); String signature = URLEncoder.encode(BaseEncoding.base64().encode(signatureBytes), UTF_8.name()); stBuilder.append("?GoogleAccessId=").append(credentials.getAccount()); stBuilder.append("&Expires=").append(expiration); stBuilder.append("&Signature=").append(signature); + return new URL(stBuilder.toString()); + } catch (MalformedURLException | UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } } + + /** + * Builds signature info. + * @param optionMap + * @param blobInfo + * @param expiration + * @param path + * @return + */ + private SignatureInfo buildSignarueInfo(Map optionMap, + BlobInfo blobInfo, long expiration, Path path) { + + HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? + (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) : HttpMethod.GET; + + SignatureInfo.Builder signatureInfoBuilder = + new SignatureInfo.Builder(httpVerb, expiration, path); + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { + checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); + signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); + } + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { + checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); + signatureInfoBuilder.setContentType(blobInfo.getContentType()); + } + + @SuppressWarnings("unchecked") + Map extHeaders = + (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) ? + (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) : + Collections.emptyMap()); + + return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + } @Override public List get(BlobId... blobIds) { diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java new file mode 100644 index 000000000000..640fa1ca47ab --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -0,0 +1,54 @@ +package com.google.cloud.storage; + +import static org.junit.Assert.assertTrue; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import org.junit.Test; +import com.google.cloud.storage.SignatureInfo.Builder; + +public class SignatureInfoTest { + + private static final String RESOURCE = "/bucketName/blobName"; + + @Test(expected = IllegalArgumentException.class) + public void requireHttpVerb() { + + new SignatureInfo.Builder(null, 0L, Paths.get(RESOURCE)).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void requireResource() { + + new SignatureInfo.Builder(HttpMethod.GET, 0L, null).build(); + } + + @Test + public void constructUnsignedPayload() { + + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + + String unsignedPayload = builder.build().constructUnsignedPayload(); + + assertTrue(("PUT\n\n\n0\n" + RESOURCE).equals(unsignedPayload)); + } + + @Test + public void constructUnsignedPayloadWithExtensionHeaders() { + + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + + Map extensionHeaders = new HashMap<>(); + extensionHeaders.put("x-goog-acl", "public-read"); + extensionHeaders.put("x-goog-meta-owner", "myself"); + + builder.setCanonicalizedExtensionHeaders(extensionHeaders); + + String unsignedPayload = builder.build().constructUnsignedPayload(); + + String rawPayload = + "PUT\n\n\n0\nx-goog-acl:public-read\nx-goog-meta-owner:myself\n" + RESOURCE; + + assertTrue(rawPayload.equals(unsignedPayload)); + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index 3c8a6ba38f0f..196850f75880 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -74,6 +74,7 @@ import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -1672,6 +1673,63 @@ public void testSignUrlForBlobWithSpecialChars() signer.verify(BaseEncoding.base64().decode(URLDecoder.decode(signature, UTF_8.name())))); } } + + @Test + public void testSignUrlWithExtHeaders() + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException, + UnsupportedEncodingException { + EasyMock.replay(storageRpcMock); + ServiceAccountCredentials credentials = + new ServiceAccountCredentials(null, ACCOUNT, privateKey, null, null); + storage = options.toBuilder().setCredentials(credentials).build().getService(); + Map extHeaders = new HashMap(); + extHeaders.put("x-goog-acl", "public-read"); + extHeaders.put("x-goog-meta-owner", "myself"); + URL url = + storage.signUrl( + BLOB_INFO1, + 14, + TimeUnit.DAYS, + Storage.SignUrlOption.httpMethod(HttpMethod.PUT), + Storage.SignUrlOption.withContentType(), + Storage.SignUrlOption.withExtHeaders(extHeaders)); + String stringUrl = url.toString(); + String expectedUrl = + new StringBuilder("https://storage.googleapis.com/") + .append(BUCKET_NAME1) + .append('/') + .append(BLOB_NAME1) + .append("?GoogleAccessId=") + .append(ACCOUNT) + .append("&Expires=") + .append(42L + 1209600) + .append("&Signature=") + .toString(); + assertTrue(stringUrl.startsWith(expectedUrl)); + String signature = stringUrl.substring(expectedUrl.length()); + + StringBuilder signedMessageBuilder = new StringBuilder(); + signedMessageBuilder + .append(HttpMethod.PUT) + .append('\n') + .append('\n') + .append(BLOB_INFO1.getContentType()) + .append('\n') + .append(42L + 1209600) + .append("\n") + .append("x-goog-acl:public-read\n") + .append("x-goog-meta-owner:myself\n") + .append("/") + .append(BUCKET_NAME1) + .append('/') + .append(BLOB_NAME1); + + Signature signer = Signature.getInstance("SHA256withRSA"); + signer.initVerify(publicKey); + signer.update(signedMessageBuilder.toString().getBytes(UTF_8)); + assertTrue( + signer.verify(BaseEncoding.base64().decode(URLDecoder.decode(signature, UTF_8.name())))); + } @Test public void testSignUrlForBlobWithSlashes() From fb62be68fdadf07036f507f0e8e0f842375d287a Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 10 Sep 2017 20:41:44 +0200 Subject: [PATCH 02/11] #2421 Added license --- .../google/cloud/storage/SignatureInfo.java | 19 ++++++- .../com/google/cloud/storage/StorageImpl.java | 55 ++++++++++--------- .../cloud/storage/SignatureInfoTest.java | 19 ++++++- 3 files changed, 64 insertions(+), 29 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index aa2eb7ace7dc..5ed0c8282966 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -1,6 +1,23 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.storage; import static com.google.common.base.Preconditions.checkArgument; + import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; @@ -36,7 +53,7 @@ private SignatureInfo(Builder builder) { /** * Constructs payload to be signed. * - * @return + * @return paylod to sign * @see https://cloud.google.com/storage/docs/access-control#Signed-URLs */ public String constructUnsignedPayload() { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index ab0e4b480c89..58ce062b012e 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -552,38 +552,39 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio /** * Builds signature info. - * @param optionMap - * @param blobInfo - * @param expiration - * @param path - * @return + * @param optionMap the option map + * @param blobInfo the blob info + * @param expiration the expiration in seconds + * @param path the resource path + * @return signature info */ private SignatureInfo buildSignarueInfo(Map optionMap, BlobInfo blobInfo, long expiration, Path path) { - - HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? - (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) : HttpMethod.GET; - + + HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) + ? (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) + : HttpMethod.GET; + SignatureInfo.Builder signatureInfoBuilder = new SignatureInfo.Builder(httpVerb, expiration, path); - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { - checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); - signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); - } - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { - checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); - signatureInfoBuilder.setContentType(blobInfo.getContentType()); - } - - @SuppressWarnings("unchecked") - Map extHeaders = - (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) ? - (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) : - Collections.emptyMap()); - - return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { + checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); + signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); + } + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { + checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); + signatureInfoBuilder.setContentType(blobInfo.getContentType()); + } + + @SuppressWarnings("unchecked") + Map extHeaders = + (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) + ? (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) + : Collections.emptyMap()); + + return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index d6f301edfc7f..655f54f20123 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -1,11 +1,28 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.storage; import static org.junit.Assert.assertEquals; + +import com.google.cloud.storage.SignatureInfo.Builder; import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import org.junit.Test; -import com.google.cloud.storage.SignatureInfo.Builder; public class SignatureInfoTest { From bd5f56681a653eaf1193b7f2bf881328ecf20751 Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 10 Sep 2017 20:41:44 +0200 Subject: [PATCH 03/11] #2421 Used URI instead Path for storage resources --- .../google/cloud/storage/SignatureInfo.java | 31 ++++++-- .../com/google/cloud/storage/StorageImpl.java | 77 ++++++++++--------- .../cloud/storage/SignatureInfoTest.java | 27 +++++-- 3 files changed, 85 insertions(+), 50 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index aa2eb7ace7dc..1df761e363ad 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -1,7 +1,24 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.storage; import static com.google.common.base.Preconditions.checkArgument; -import java.nio.file.Path; + +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -22,7 +39,7 @@ public class SignatureInfo { private final String contentType; private final long expiration; private final Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; private SignatureInfo(Builder builder) { this.httpVerb = builder.httpVerb; @@ -36,7 +53,7 @@ private SignatureInfo(Builder builder) { /** * Constructs payload to be signed. * - * @return + * @return paylod to sign * @see https://cloud.google.com/storage/docs/access-control#Signed-URLs */ public String constructUnsignedPayload() { @@ -90,7 +107,7 @@ public Map getCanonicalizedExtensionHeaders() { return canonicalizedExtensionHeaders; } - public Path getCanonicalizedResource() { + public URI getCanonicalizedResource() { return canonicalizedResource; } @@ -101,17 +118,17 @@ public final static class Builder { private String contentType; private final long expiration; private Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; /** * Constructs builder. * * @param httpVerb the HTTP method * @param expiration the EPOX expiration date - * @param canonicalizedResource the resource path + * @param canonicalizedResource the resource URI * @throws IllegalArgumentException if required field is not provided. */ - public Builder(HttpMethod httpVerb, long expiration, Path canonicalizedResource) { + public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) { checkArgument(httpVerb != null, "Required HTTP method"); checkArgument(canonicalizedResource != null, "Required canonicalized resource"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index ab0e4b480c89..11a23a749470 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -66,10 +66,9 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLEncoder; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -529,22 +528,23 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); stPath.append(escapedName.replace("?", "%3F")); - - Path path = Paths.get(stPath.toString()); - + + URI resource = URI.create(stPath.toString()); + try { - SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); + SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, resource); byte[] signatureBytes = credentials.sign(signatureInfo.constructUnsignedPayload().getBytes(UTF_8)); - StringBuilder stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); + StringBuilder stBuilder = + new StringBuilder("https://storage.googleapis.com").append(resource); String signature = URLEncoder.encode(BaseEncoding.base64().encode(signatureBytes), UTF_8.name()); stBuilder.append("?GoogleAccessId=").append(credentials.getAccount()); stBuilder.append("&Expires=").append(expiration); stBuilder.append("&Signature=").append(signature); - + return new URL(stBuilder.toString()); - + } catch (MalformedURLException | UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } @@ -552,38 +552,39 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio /** * Builds signature info. - * @param optionMap - * @param blobInfo - * @param expiration - * @param path - * @return + * @param optionMap the option map + * @param blobInfo the blob info + * @param expiration the expiration in seconds + * @param resource the resource URI + * @return signature info */ private SignatureInfo buildSignarueInfo(Map optionMap, - BlobInfo blobInfo, long expiration, Path path) { - - HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? - (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) : HttpMethod.GET; - + BlobInfo blobInfo, long expiration, URI resource) { + + HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) + ? (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) + : HttpMethod.GET; + SignatureInfo.Builder signatureInfoBuilder = - new SignatureInfo.Builder(httpVerb, expiration, path); - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { - checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); - signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); - } - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { - checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); - signatureInfoBuilder.setContentType(blobInfo.getContentType()); - } - - @SuppressWarnings("unchecked") - Map extHeaders = - (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) ? - (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) : - Collections.emptyMap()); - - return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + new SignatureInfo.Builder(httpVerb, expiration, resource); + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { + checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); + signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); + } + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { + checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); + signatureInfoBuilder.setContentType(blobInfo.getContentType()); + } + + @SuppressWarnings("unchecked") + Map extHeaders = + (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) + ? (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) + : Collections.emptyMap()); + + return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index d6f301edfc7f..cd27b6aa805e 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -1,11 +1,28 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.google.cloud.storage; import static org.junit.Assert.assertEquals; -import java.nio.file.Paths; + +import com.google.cloud.storage.SignatureInfo.Builder; +import java.net.URI; import java.util.HashMap; import java.util.Map; import org.junit.Test; -import com.google.cloud.storage.SignatureInfo.Builder; public class SignatureInfoTest { @@ -14,7 +31,7 @@ public class SignatureInfoTest { @Test(expected = IllegalArgumentException.class) public void requireHttpVerb() { - new SignatureInfo.Builder(null, 0L, Paths.get(RESOURCE)).build(); + new SignatureInfo.Builder(null, 0L, URI.create(RESOURCE)).build(); } @Test(expected = IllegalArgumentException.class) @@ -26,7 +43,7 @@ public void requireResource() { @Test public void constructUnsignedPayload() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); String unsignedPayload = builder.build().constructUnsignedPayload(); @@ -36,7 +53,7 @@ public void constructUnsignedPayload() { @Test public void constructUnsignedPayloadWithExtensionHeaders() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); Map extensionHeaders = new HashMap<>(); extensionHeaders.put("x-goog-acl", "public-read"); From 54c03bd869f7c8b8b11e79cdbf77d995ee6f333e Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 10 Sep 2017 21:32:35 +0200 Subject: [PATCH 04/11] Revert " #2421 Used URI instead Path for storage resources" This reverts commit bd5f56681a653eaf1193b7f2bf881328ecf20751. --- .../google/cloud/storage/SignatureInfo.java | 31 ++------ .../com/google/cloud/storage/StorageImpl.java | 77 +++++++++---------- .../cloud/storage/SignatureInfoTest.java | 27 ++----- 3 files changed, 50 insertions(+), 85 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index 1df761e363ad..aa2eb7ace7dc 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -1,24 +1,7 @@ -/* - * Copyright 2015 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.google.cloud.storage; import static com.google.common.base.Preconditions.checkArgument; - -import java.net.URI; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,7 +22,7 @@ public class SignatureInfo { private final String contentType; private final long expiration; private final Map canonicalizedExtensionHeaders; - private final URI canonicalizedResource; + private final Path canonicalizedResource; private SignatureInfo(Builder builder) { this.httpVerb = builder.httpVerb; @@ -53,7 +36,7 @@ private SignatureInfo(Builder builder) { /** * Constructs payload to be signed. * - * @return paylod to sign + * @return * @see https://cloud.google.com/storage/docs/access-control#Signed-URLs */ public String constructUnsignedPayload() { @@ -107,7 +90,7 @@ public Map getCanonicalizedExtensionHeaders() { return canonicalizedExtensionHeaders; } - public URI getCanonicalizedResource() { + public Path getCanonicalizedResource() { return canonicalizedResource; } @@ -118,17 +101,17 @@ public final static class Builder { private String contentType; private final long expiration; private Map canonicalizedExtensionHeaders; - private final URI canonicalizedResource; + private final Path canonicalizedResource; /** * Constructs builder. * * @param httpVerb the HTTP method * @param expiration the EPOX expiration date - * @param canonicalizedResource the resource URI + * @param canonicalizedResource the resource path * @throws IllegalArgumentException if required field is not provided. */ - public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) { + public Builder(HttpMethod httpVerb, long expiration, Path canonicalizedResource) { checkArgument(httpVerb != null, "Required HTTP method"); checkArgument(canonicalizedResource != null, "Required canonicalized resource"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 11a23a749470..ab0e4b480c89 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -66,9 +66,10 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; -import java.net.URI; import java.net.URL; import java.net.URLEncoder; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -528,23 +529,22 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); stPath.append(escapedName.replace("?", "%3F")); - - URI resource = URI.create(stPath.toString()); - + + Path path = Paths.get(stPath.toString()); + try { - SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, resource); + SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); byte[] signatureBytes = credentials.sign(signatureInfo.constructUnsignedPayload().getBytes(UTF_8)); - StringBuilder stBuilder = - new StringBuilder("https://storage.googleapis.com").append(resource); + StringBuilder stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); String signature = URLEncoder.encode(BaseEncoding.base64().encode(signatureBytes), UTF_8.name()); stBuilder.append("?GoogleAccessId=").append(credentials.getAccount()); stBuilder.append("&Expires=").append(expiration); stBuilder.append("&Signature=").append(signature); - + return new URL(stBuilder.toString()); - + } catch (MalformedURLException | UnsupportedEncodingException ex) { throw new IllegalStateException(ex); } @@ -552,39 +552,38 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio /** * Builds signature info. - * @param optionMap the option map - * @param blobInfo the blob info - * @param expiration the expiration in seconds - * @param resource the resource URI - * @return signature info + * @param optionMap + * @param blobInfo + * @param expiration + * @param path + * @return */ private SignatureInfo buildSignarueInfo(Map optionMap, - BlobInfo blobInfo, long expiration, URI resource) { - - HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) - ? (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) - : HttpMethod.GET; - + BlobInfo blobInfo, long expiration, Path path) { + + HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? + (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) : HttpMethod.GET; + SignatureInfo.Builder signatureInfoBuilder = - new SignatureInfo.Builder(httpVerb, expiration, resource); - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { - checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); - signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); - } - - if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { - checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); - signatureInfoBuilder.setContentType(blobInfo.getContentType()); - } - - @SuppressWarnings("unchecked") - Map extHeaders = - (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) - ? (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) - : Collections.emptyMap()); - - return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); + new SignatureInfo.Builder(httpVerb, expiration, path); + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.MD5), false)) { + checkArgument(blobInfo.getMd5() != null, "Blob is missing a value for md5"); + signatureInfoBuilder.setContentMd5(blobInfo.getMd5()); + } + + if (firstNonNull((Boolean) optionMap.get(SignUrlOption.Option.CONTENT_TYPE), false)) { + checkArgument(blobInfo.getContentType() != null, "Blob is missing a value for content-type"); + signatureInfoBuilder.setContentType(blobInfo.getContentType()); + } + + @SuppressWarnings("unchecked") + Map extHeaders = + (Map) (optionMap.containsKey(SignUrlOption.Option.EXT_HEADERS) ? + (Map) optionMap.get(SignUrlOption.Option.EXT_HEADERS) : + Collections.emptyMap()); + + return signatureInfoBuilder.setCanonicalizedExtensionHeaders(extHeaders).build(); } @Override diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index cd27b6aa805e..d6f301edfc7f 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -1,28 +1,11 @@ -/* - * Copyright 2015 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - package com.google.cloud.storage; import static org.junit.Assert.assertEquals; - -import com.google.cloud.storage.SignatureInfo.Builder; -import java.net.URI; +import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; import org.junit.Test; +import com.google.cloud.storage.SignatureInfo.Builder; public class SignatureInfoTest { @@ -31,7 +14,7 @@ public class SignatureInfoTest { @Test(expected = IllegalArgumentException.class) public void requireHttpVerb() { - new SignatureInfo.Builder(null, 0L, URI.create(RESOURCE)).build(); + new SignatureInfo.Builder(null, 0L, Paths.get(RESOURCE)).build(); } @Test(expected = IllegalArgumentException.class) @@ -43,7 +26,7 @@ public void requireResource() { @Test public void constructUnsignedPayload() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); String unsignedPayload = builder.build().constructUnsignedPayload(); @@ -53,7 +36,7 @@ public void constructUnsignedPayload() { @Test public void constructUnsignedPayloadWithExtensionHeaders() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); Map extensionHeaders = new HashMap<>(); extensionHeaders.put("x-goog-acl", "public-read"); From a0b6e2aad652c0032ca335e995067f25fb649ccd Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 10 Sep 2017 21:47:39 +0200 Subject: [PATCH 05/11] #2421 Used URI instead Path for storage resources --- .../com/google/cloud/storage/SignatureInfo.java | 14 +++++++------- .../java/com/google/cloud/storage/StorageImpl.java | 9 ++++----- .../google/cloud/storage/SignatureInfoTest.java | 8 ++++---- 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index 5ed0c8282966..b8f8f8117688 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; -import java.nio.file.Path; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -39,7 +39,7 @@ public class SignatureInfo { private final String contentType; private final long expiration; private final Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; private SignatureInfo(Builder builder) { this.httpVerb = builder.httpVerb; @@ -107,28 +107,28 @@ public Map getCanonicalizedExtensionHeaders() { return canonicalizedExtensionHeaders; } - public Path getCanonicalizedResource() { + public URI getCanonicalizedResource() { return canonicalizedResource; } - public final static class Builder { + public static final class Builder { private final HttpMethod httpVerb; private String contentMd5; private String contentType; private final long expiration; private Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; /** * Constructs builder. * * @param httpVerb the HTTP method * @param expiration the EPOX expiration date - * @param canonicalizedResource the resource path + * @param canonicalizedResource the resource URI * @throws IllegalArgumentException if required field is not provided. */ - public Builder(HttpMethod httpVerb, long expiration, Path canonicalizedResource) { + public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) { checkArgument(httpVerb != null, "Required HTTP method"); checkArgument(canonicalizedResource != null, "Required canonicalized resource"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 58ce062b012e..264117b8e943 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -66,10 +66,9 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLEncoder; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -530,7 +529,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); stPath.append(escapedName.replace("?", "%3F")); - Path path = Paths.get(stPath.toString()); + URI path = URI.create(stPath.toString()); try { SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); @@ -555,11 +554,11 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio * @param optionMap the option map * @param blobInfo the blob info * @param expiration the expiration in seconds - * @param path the resource path + * @param path the resource URI * @return signature info */ private SignatureInfo buildSignarueInfo(Map optionMap, - BlobInfo blobInfo, long expiration, Path path) { + BlobInfo blobInfo, long expiration, URI path) { HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index 655f54f20123..cd27b6aa805e 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; import com.google.cloud.storage.SignatureInfo.Builder; -import java.nio.file.Paths; +import java.net.URI; import java.util.HashMap; import java.util.Map; import org.junit.Test; @@ -31,7 +31,7 @@ public class SignatureInfoTest { @Test(expected = IllegalArgumentException.class) public void requireHttpVerb() { - new SignatureInfo.Builder(null, 0L, Paths.get(RESOURCE)).build(); + new SignatureInfo.Builder(null, 0L, URI.create(RESOURCE)).build(); } @Test(expected = IllegalArgumentException.class) @@ -43,7 +43,7 @@ public void requireResource() { @Test public void constructUnsignedPayload() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); String unsignedPayload = builder.build().constructUnsignedPayload(); @@ -53,7 +53,7 @@ public void constructUnsignedPayload() { @Test public void constructUnsignedPayloadWithExtensionHeaders() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); Map extensionHeaders = new HashMap<>(); extensionHeaders.put("x-goog-acl", "public-read"); From 0aec379eaf4337f9bc435d7cb6ad280a46008d6c Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 10 Sep 2017 21:47:39 +0200 Subject: [PATCH 06/11] #2421 Fixed see URLs --- .../google/cloud/storage/SignatureInfo.java | 20 ++++++++++--------- .../com/google/cloud/storage/Storage.java | 3 +-- .../com/google/cloud/storage/StorageImpl.java | 11 +++++----- .../cloud/storage/SignatureInfoTest.java | 8 ++++---- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index 5ed0c8282966..c9dfe45631f0 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -18,7 +18,7 @@ import static com.google.common.base.Preconditions.checkArgument; -import java.nio.file.Path; +import java.net.URI; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -27,7 +27,9 @@ /** * Signature Info holds payload components of the string that requires signing. * - * @see https://cloud.google.com/storage/docs/access-control/signed-urls#string-components + * @see + * Components */ public class SignatureInfo { @@ -39,7 +41,7 @@ public class SignatureInfo { private final String contentType; private final long expiration; private final Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; private SignatureInfo(Builder builder) { this.httpVerb = builder.httpVerb; @@ -54,7 +56,7 @@ private SignatureInfo(Builder builder) { * Constructs payload to be signed. * * @return paylod to sign - * @see https://cloud.google.com/storage/docs/access-control#Signed-URLs + * @see Signed URLs */ public String constructUnsignedPayload() { StringBuilder payload = new StringBuilder(); @@ -107,28 +109,28 @@ public Map getCanonicalizedExtensionHeaders() { return canonicalizedExtensionHeaders; } - public Path getCanonicalizedResource() { + public URI getCanonicalizedResource() { return canonicalizedResource; } - public final static class Builder { + public static final class Builder { private final HttpMethod httpVerb; private String contentMd5; private String contentType; private final long expiration; private Map canonicalizedExtensionHeaders; - private final Path canonicalizedResource; + private final URI canonicalizedResource; /** * Constructs builder. * * @param httpVerb the HTTP method * @param expiration the EPOX expiration date - * @param canonicalizedResource the resource path + * @param canonicalizedResource the resource URI * @throws IllegalArgumentException if required field is not provided. */ - public Builder(HttpMethod httpVerb, long expiration, Path canonicalizedResource) { + public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) { checkArgument(httpVerb != null, "Required HTTP method"); checkArgument(canonicalizedResource != null, "Required canonicalized resource"); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index 21fa68c054f3..5fb4e725db3a 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -898,8 +898,7 @@ public static SignUrlOption withMd5() { * Use it if signature should include the blob's canonicalized extended headers. * When used, users of the signed URL should include the canonicalized extended headers with * their request. - * - * @see /a> + * @see */ public static SignUrlOption withExtHeaders(Map extHeaders) { return new SignUrlOption(Option.EXT_HEADERS, extHeaders); diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 58ce062b012e..f67f11369566 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -66,10 +66,9 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URI; import java.net.URL; import java.net.URLEncoder; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -530,7 +529,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); stPath.append(escapedName.replace("?", "%3F")); - Path path = Paths.get(stPath.toString()); + URI path = URI.create(stPath.toString()); try { SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); @@ -555,11 +554,11 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio * @param optionMap the option map * @param blobInfo the blob info * @param expiration the expiration in seconds - * @param path the resource path - * @return signature info + * @param path the resource URI + * @return signature info */ private SignatureInfo buildSignarueInfo(Map optionMap, - BlobInfo blobInfo, long expiration, Path path) { + BlobInfo blobInfo, long expiration, URI path) { HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) ? (HttpMethod) optionMap.get(SignUrlOption.Option.HTTP_METHOD) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java index 655f54f20123..cd27b6aa805e 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/SignatureInfoTest.java @@ -19,7 +19,7 @@ import static org.junit.Assert.assertEquals; import com.google.cloud.storage.SignatureInfo.Builder; -import java.nio.file.Paths; +import java.net.URI; import java.util.HashMap; import java.util.Map; import org.junit.Test; @@ -31,7 +31,7 @@ public class SignatureInfoTest { @Test(expected = IllegalArgumentException.class) public void requireHttpVerb() { - new SignatureInfo.Builder(null, 0L, Paths.get(RESOURCE)).build(); + new SignatureInfo.Builder(null, 0L, URI.create(RESOURCE)).build(); } @Test(expected = IllegalArgumentException.class) @@ -43,7 +43,7 @@ public void requireResource() { @Test public void constructUnsignedPayload() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); String unsignedPayload = builder.build().constructUnsignedPayload(); @@ -53,7 +53,7 @@ public void constructUnsignedPayload() { @Test public void constructUnsignedPayloadWithExtensionHeaders() { - Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, Paths.get(RESOURCE)); + Builder builder = new SignatureInfo.Builder(HttpMethod.PUT, 0L, URI.create(RESOURCE)); Map extensionHeaders = new HashMap<>(); extensionHeaders.put("x-goog-acl", "public-read"); From 43a8a7d693597f0c4f2683f1ff3871ff4dad873d Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 31 Dec 2017 12:13:55 +0100 Subject: [PATCH 07/11] #2422 Fixed typo --- .../src/main/java/com/google/cloud/storage/StorageImpl.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index f67f11369566..49a412c6ab13 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -532,7 +532,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio URI path = URI.create(stPath.toString()); try { - SignatureInfo signatureInfo = buildSignarueInfo(optionMap, blobInfo, expiration, path); + SignatureInfo signatureInfo = buildSignatureInfo(optionMap, blobInfo, expiration, path); byte[] signatureBytes = credentials.sign(signatureInfo.constructUnsignedPayload().getBytes(UTF_8)); StringBuilder stBuilder = new StringBuilder("https://storage.googleapis.com").append(path); @@ -557,7 +557,7 @@ public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOptio * @param path the resource URI * @return signature info */ - private SignatureInfo buildSignarueInfo(Map optionMap, + private SignatureInfo buildSignatureInfo(Map optionMap, BlobInfo blobInfo, long expiration, URI path) { HttpMethod httpVerb = optionMap.containsKey(SignUrlOption.Option.HTTP_METHOD) From 7271dfd622b8dbb01a91926b8c9246942a186c24 Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Sun, 31 Dec 2017 12:35:38 +0100 Subject: [PATCH 08/11] #2422 Added checks to ensure that required fields (verb, resource, expiration) are non-null in the build() method --- .../main/java/com/google/cloud/storage/SignatureInfo.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index c9dfe45631f0..a92e668558ee 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -131,9 +131,6 @@ public static final class Builder { * @throws IllegalArgumentException if required field is not provided. */ public Builder(HttpMethod httpVerb, long expiration, URI canonicalizedResource) { - checkArgument(httpVerb != null, "Required HTTP method"); - checkArgument(canonicalizedResource != null, "Required canonicalized resource"); - this.httpVerb = httpVerb; this.expiration = expiration; this.canonicalizedResource = canonicalizedResource; @@ -171,6 +168,10 @@ public Builder setCanonicalizedExtensionHeaders( * Creates an {@code SignatureInfo} object from this builder. */ public SignatureInfo build() { + checkArgument(httpVerb != null, "Required HTTP method"); + checkArgument(canonicalizedResource != null, "Required canonicalized resource"); + checkArgument(expiration >= 0, "Expiration must be greater than or equal to zero"); + return new SignatureInfo(this); } } From fdf91c400651c0f9e9573771a67960730d902bdf Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Mon, 1 Jan 2018 14:16:29 +0100 Subject: [PATCH 09/11] #2422 Canonical extension headers serialization --- .../CanonicalExtensionHeadersSerializer.java | 82 +++++++++++++++++++ .../google/cloud/storage/SignatureInfo.java | 18 +--- ...nonicalExtensionHeadersSerializerTest.java | 77 +++++++++++++++++ 3 files changed, 163 insertions(+), 14 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java new file mode 100644 index 000000000000..39eff5bcbb9c --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java @@ -0,0 +1,82 @@ +/* + * Copyright 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Canonical extension header serializer. + * + * @see Canonical Extension Headers + */ +public class CanonicalExtensionHeadersSerializer { + + private static final char HEADER_SEPARATOR = ':'; + + public StringBuilder serialize(Map canonicalizedExtensionHeaders) { + + StringBuilder serializedHeaders = new StringBuilder(); + + if (canonicalizedExtensionHeaders == null || + canonicalizedExtensionHeaders.isEmpty()) { + + return serializedHeaders; + } + + // Make all custom header names lowercase. + Map lowercaseHeaders = new HashMap<>(); + for (String headerName : new ArrayList<>(canonicalizedExtensionHeaders.keySet())) { + + String lowercaseHeaderName = headerName.toLowerCase(); + + // If present, remove the x-goog-encryption-key and x-goog-encryption-key-sha256 headers. + if ("x-goog-encryption-key".equals(lowercaseHeaderName) || + "x-goog-encryption-key-sha256".equals(lowercaseHeaderName)) { + + continue; + } + + lowercaseHeaders.put(lowercaseHeaderName, canonicalizedExtensionHeaders.get(headerName)); + } + + // Sort all custom headers by header name using a lexicographical sort by code point value. + List sortedHeaderNames = new ArrayList<>(lowercaseHeaders.keySet()); + Collections.sort(sortedHeaderNames); + + for (String headerName : sortedHeaderNames) { + serializedHeaders + .append(headerName).append(HEADER_SEPARATOR) + .append(lowercaseHeaders.get(headerName) + // Remove any whitespace around the colon that appears after the header name. + .trim() + // Replace any folding whitespace or newlines (CRLF or LF) with a single space. + .replaceAll("[\\s]{2,}"," ") + .replaceAll("(\\t|\\r?\\n)+", " ")) + // Append a newline (U+000A) to each custom header. + .append(SignatureInfo.COMPONENT_SEPARATOR); + } + + // Concatenate all custom headers + return serializedHeaders; + } +} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java index a92e668558ee..bd9ae3ee34c8 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/SignatureInfo.java @@ -17,11 +17,7 @@ package com.google.cloud.storage; import static com.google.common.base.Preconditions.checkArgument; - import java.net.URI; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.Map; /** @@ -33,8 +29,7 @@ */ public class SignatureInfo { - private static final char COMPONENT_SEPARATOR = '\n'; - private static final char HEADER_SEPARATOR = ':'; + public static final char COMPONENT_SEPARATOR = '\n'; private final HttpMethod httpVerb; private final String contentMd5; @@ -75,15 +70,10 @@ public String constructUnsignedPayload() { payload.append(expiration).append(COMPONENT_SEPARATOR); if (canonicalizedExtensionHeaders != null) { - - List orderedKeys = new ArrayList<>(canonicalizedExtensionHeaders.keySet()); - Collections.sort(orderedKeys); - - for (String key : orderedKeys) { - payload.append(key).append(HEADER_SEPARATOR).append(canonicalizedExtensionHeaders.get(key)) - .append(COMPONENT_SEPARATOR); - } + payload.append(new CanonicalExtensionHeadersSerializer() + .serialize(canonicalizedExtensionHeaders)); } + payload.append(canonicalizedResource); return payload.toString(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java new file mode 100644 index 000000000000..fa2c2dc3a0e1 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2015 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud.storage; + +import static org.junit.Assert.assertEquals; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import org.junit.Before; +import org.junit.Test; + +public class CanonicalExtensionHeadersSerializerTest { + + private CanonicalExtensionHeadersSerializer serializer; + + @Before + public void setUp() { + serializer = new CanonicalExtensionHeadersSerializer(); + } + + @Test + public void given_no_headers_when_serialize_then_produce_empty_string() { + + StringBuilder sb = serializer.serialize(Collections.emptyMap()); + + assertEquals(sb.toString(), ""); + } + + @Test + public void given_null_headers_when_serialize_then_produce_empty_string() { + + StringBuilder sb = serializer.serialize(null); + + assertEquals(sb.toString(), ""); + } + + @Test + public void given_encryption_headers_when_serialize_then_are_remvoed() { + + Map encryptionHeaders = new HashMap<>(); + encryptionHeaders.put("x-goog-encryption-key", ""); + encryptionHeaders.put("x-goog-encryption-key-sha256", ""); + + StringBuilder sb = serializer.serialize(encryptionHeaders); + + assertEquals(sb.toString(), ""); + } + + @Test + public void given_headers_when_serialize_then_success() { + + Map encryptionHeaders = new HashMap<>(); + encryptionHeaders.put("x-goog-encryption-key", ""); + encryptionHeaders.put("x-GOOg-acl", " \n public-read "); + encryptionHeaders.put("x-goog-encryption-key-sha256", ""); + encryptionHeaders.put("X-goog-meta-OWNER", " myself and others \n"); + + StringBuilder sb = serializer.serialize(encryptionHeaders); + + assertEquals(sb.toString(), "x-goog-acl:public-read\nx-goog-meta-owner:myself and others\n"); + } +} From 932d552d582ab9ed4aedaa2f31ed50a1e63dcd62 Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Mon, 1 Jan 2018 17:06:37 +0100 Subject: [PATCH 10/11] #2422 Refactoring underscores to camelcase --- .../storage/CanonicalExtensionHeadersSerializerTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java index fa2c2dc3a0e1..599c9a5ba16d 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializerTest.java @@ -34,7 +34,7 @@ public void setUp() { } @Test - public void given_no_headers_when_serialize_then_produce_empty_string() { + public void givenNoHeadersWhenSerializeThenProduceNothing() { StringBuilder sb = serializer.serialize(Collections.emptyMap()); @@ -42,7 +42,7 @@ public void given_no_headers_when_serialize_then_produce_empty_string() { } @Test - public void given_null_headers_when_serialize_then_produce_empty_string() { + public void givenNullHeadersWhenSerializeThenProduceNothing() { StringBuilder sb = serializer.serialize(null); @@ -50,7 +50,7 @@ public void given_null_headers_when_serialize_then_produce_empty_string() { } @Test - public void given_encryption_headers_when_serialize_then_are_remvoed() { + public void givenEncryptionHeadersWhenSerializeThenAreRemvoed() { Map encryptionHeaders = new HashMap<>(); encryptionHeaders.put("x-goog-encryption-key", ""); @@ -62,7 +62,7 @@ public void given_encryption_headers_when_serialize_then_are_remvoed() { } @Test - public void given_headers_when_serialize_then_success() { + public void givenHeadersWhenSerializeThenSuccess() { Map encryptionHeaders = new HashMap<>(); encryptionHeaders.put("x-goog-encryption-key", ""); From d7f67c25a51ede4d3cd2eec8f7cf52c373e22865 Mon Sep 17 00:00:00 2001 From: Fran Tello Date: Tue, 23 Jan 2018 23:01:55 +0100 Subject: [PATCH 11/11] Fixed URL inline --- .../cloud/storage/CanonicalExtensionHeadersSerializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java index 39eff5bcbb9c..5bcadcf9e3af 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/CanonicalExtensionHeadersSerializer.java @@ -26,8 +26,8 @@ * Canonical extension header serializer. * * @see Canonical Extension Headers + * "https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers"> + * Canonical Extension Headers */ public class CanonicalExtensionHeadersSerializer {