From 5b38ded4f972fed6da2ed86db2c69963506b0eb5 Mon Sep 17 00:00:00 2001 From: Praful Makani Date: Mon, 12 Nov 2018 20:29:58 +0530 Subject: [PATCH] removed overload method and add method withExtHostName in SignUrlOption --- .../com/google/cloud/storage/Storage.java | 74 ++---------- .../com/google/cloud/storage/StorageImpl.java | 107 +++++++++--------- .../google/cloud/storage/StorageImplTest.java | 16 +-- 3 files changed, 70 insertions(+), 127 deletions(-) diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java index f354072f9fd3..f670f513c8b9 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/Storage.java @@ -892,7 +892,7 @@ class SignUrlOption implements Serializable { private final Object value; enum Option { - HTTP_METHOD, CONTENT_TYPE, MD5, EXT_HEADERS, SERVICE_ACCOUNT_CRED + HTTP_METHOD, CONTENT_TYPE, MD5, EXT_HEADERS, SERVICE_ACCOUNT_CRED,EXT_HOST } private SignUrlOption(Option option, Object value) { @@ -953,6 +953,13 @@ public static SignUrlOption withExtHeaders(Map extHeaders) { public static SignUrlOption signWith(ServiceAccountSigner signer) { return new SignUrlOption(Option.SERVICE_ACCOUNT_CRED, signer); } + + /** + * Provides a host name to sign the URL. If not provided than host name will be default + */ + public static SignUrlOption withExtHostName(String extHostName){ + return new SignUrlOption(Option.EXT_HOST, extHostName); + } } /** @@ -2107,6 +2114,7 @@ public static Builder newBuilder() { * granularity supported is 1 second, finer granularities will be truncated * @param unit time unit of the {@code duration} parameter * @param options optional URL signing options + * {@code SignUrlOption.withExtHostName()} option is used for external host name of signed url * @throws IllegalStateException if {@link SignUrlOption#signWith(ServiceAccountSigner)} was not * used and no implementation of {@link ServiceAccountSigner} was provided to * {@link StorageOptions} @@ -2119,70 +2127,6 @@ public static Builder newBuilder() { */ URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options); - /** - * Generates a signed URL for a blob. If you have a blob that you want to allow access to for a - * fixed amount of time, you can use this method to generate a URL that is only valid within a - * certain time period. This is particularly useful if you don't want publicly accessible blobs, - * but also don't want to require users to explicitly log in. Signing a URL requires - * a service account signer. If an instance of {@link com.google.auth.ServiceAccountSigner} was - * passed to {@link StorageOptions}' builder via {@code setCredentials(Credentials)} or the - * default credentials are being used and the environment variable - * {@code GOOGLE_APPLICATION_CREDENTIALS} is set or your application is running in App Engine, - * then {@code signUrl} will use that credentials to sign the URL. If the credentials passed to - * {@link StorageOptions} do not implement {@link ServiceAccountSigner} (this is the case, for - * instance, for Google Cloud SDK credentials) then {@code signUrl} will throw an - * {@link IllegalStateException} unless an implementation of {@link ServiceAccountSigner} is - * passed using the {@link SignUrlOption#signWith(ServiceAccountSigner)} option. - * - *

A service account signer is looked for in the following order: - *

    - *
  1. The signer passed with the option {@link SignUrlOption#signWith(ServiceAccountSigner)} - *
  2. The credentials passed to {@link StorageOptions} - *
  3. The default credentials, if no credentials were passed to {@link StorageOptions} - *
- * - *

Example of creating a signed URL that is valid for 2 weeks, using the default credentials - * for signing the URL. - *

 {@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
-   * URL signedUrl = storage.signUrl(BlobInfo.newBuilder(bucketName, blobName).build(), 14,
-   *     TimeUnit.DAYS);
-   * }
- * - *

Example of creating a signed URL passing the - * {@link SignUrlOption#signWith(ServiceAccountSigner)} option, that will be used for signing the - * URL. - *

 {@code
-   * String bucketName = "my_unique_bucket";
-   * String blobName = "my_blob_name";
-   * String keyPath = "/path/to/key.json";
-   * URL signedUrl = storage.signUrl(BlobInfo.newBuilder(bucketName, blobName).build(),
-   *     14, TimeUnit.DAYS, SignUrlOption.signWith(
-   *         ServiceAccountCredentials.fromStream(new FileInputStream(keyPath))));
-   * }
- * - *

Note that the {@link ServiceAccountSigner} may require additional configuration to enable - * URL signing. See the documentation for the implementation for more details.

- * - * @param url can be customize - * @param blobInfo the blob associated with the signed URL - * @param duration time until the signed URL expires, expressed in {@code unit}. The finest - * granularity supported is 1 second, finer granularities will be truncated - * @param unit time unit of the {@code duration} parameter - * @param options optional URL signing options - * @throws IllegalStateException if {@link SignUrlOption#signWith(ServiceAccountSigner)} was not - * used and no implementation of {@link ServiceAccountSigner} was provided to - * {@link StorageOptions} - * @throws IllegalArgumentException if {@code SignUrlOption.withMd5()} option is used and - * {@code blobInfo.md5()} is {@code null} - * @throws IllegalArgumentException if {@code SignUrlOption.withContentType()} option is used and - * {@code blobInfo.contentType()} is {@code null} - * @throws SigningException if the attempt to sign the URL failed - * @see Signed-URLs - */ - URL signUrl(String url, BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options); - /** * Gets the requested blobs. A batch request is used to perform this call. * diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java index 9d3809de23c8..f359e3a7d5c6 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/StorageImpl.java @@ -33,6 +33,22 @@ import static com.google.common.base.Preconditions.checkState; import static java.nio.charset.StandardCharsets.UTF_8; +import java.io.ByteArrayInputStream; +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.util.Arrays; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + import com.google.api.gax.paging.Page; import com.google.api.services.storage.model.BucketAccessControl; import com.google.api.services.storage.model.ObjectAccessControl; @@ -62,21 +78,6 @@ import com.google.common.io.BaseEncoding; import com.google.common.net.UrlEscapers; import com.google.common.primitives.Ints; -import java.io.ByteArrayInputStream; -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.util.Arrays; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; final class StorageImpl extends BaseService implements Storage { @@ -500,52 +501,50 @@ private BlobWriteChannel writer(BlobInfo blobInfo, BlobTargetOption... options) @Override public URL signUrl(BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) { - return signUrlOptions(DEFAULT_STORAGE_HOST, blobInfo, duration, unit, options); - } - - @Override - public URL signUrl(String url, BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options) { - return signUrlOptions(url, blobInfo, duration, unit, options); - } - - private URL signUrlOptions(String url, BlobInfo blobInfo, long duration, TimeUnit unit, SignUrlOption... options){ EnumMap optionMap = Maps.newEnumMap(SignUrlOption.Option.class); - for (SignUrlOption option : options) { - optionMap.put(option.getOption(), option.getValue()); - } - ServiceAccountSigner credentials = - (ServiceAccountSigner) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED); - if (credentials == null) { - checkState(this.getOptions().getCredentials() instanceof ServiceAccountSigner, - "Signing key was not provided and could not be derived"); - credentials = (ServiceAccountSigner) this.getOptions().getCredentials(); - } - - long expiration = TimeUnit.SECONDS.convert( - getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); + for (SignUrlOption option : options) { + optionMap.put(option.getOption(), option.getValue()); + } + ServiceAccountSigner credentials = + (ServiceAccountSigner) optionMap.get(SignUrlOption.Option.SERVICE_ACCOUNT_CRED); + if (credentials == null) { + checkState(this.getOptions().getCredentials() instanceof ServiceAccountSigner, + "Signing key was not provided and could not be derived"); + credentials = (ServiceAccountSigner) this.getOptions().getCredentials(); + } - StringBuilder stPath = new StringBuilder(); - if (!blobInfo.getBucket().startsWith(PATH_DELIMITER)) { - stPath.append(PATH_DELIMITER); - } - stPath.append(blobInfo.getBucket()); - if (!blobInfo.getBucket().endsWith(PATH_DELIMITER)) { - stPath.append(PATH_DELIMITER); - } - if (blobInfo.getName().startsWith(PATH_DELIMITER)) { - stPath.setLength(stPath.length() - 1); - } + long expiration = TimeUnit.SECONDS.convert( + getOptions().getClock().millisTime() + unit.toMillis(duration), TimeUnit.MILLISECONDS); - String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); - stPath.append(escapedName.replace("?", "%3F")); + StringBuilder stPath = new StringBuilder(); + if (!blobInfo.getBucket().startsWith(PATH_DELIMITER)) { + stPath.append(PATH_DELIMITER); + } + stPath.append(blobInfo.getBucket()); + if (!blobInfo.getBucket().endsWith(PATH_DELIMITER)) { + stPath.append(PATH_DELIMITER); + } + if (blobInfo.getName().startsWith(PATH_DELIMITER)) { + stPath.setLength(stPath.length() - 1); + } - URI path = URI.create(stPath.toString()); + String escapedName = UrlEscapers.urlFragmentEscaper().escape(blobInfo.getName()); + stPath.append(escapedName.replace("?", "%3F")); + + URI path = URI.create(stPath.toString()); - try { + try { SignatureInfo signatureInfo = buildSignatureInfo(optionMap, blobInfo, expiration, path); byte[] signatureBytes = credentials.sign(signatureInfo.constructUnsignedPayload().getBytes(UTF_8)); - StringBuilder stBuilder = new StringBuilder(url).append(path); + StringBuilder stBuilder = new StringBuilder(); + if(optionMap.get(SignUrlOption.Option.EXT_HOST) == null){ + stBuilder.append(DEFAULT_STORAGE_HOST).append(path); + } + else{ + stBuilder.append(optionMap.get(SignUrlOption.Option.EXT_HOST).toString()).append(path); + } + String signature = URLEncoder.encode(BaseEncoding.base64().encode(signatureBytes), UTF_8.name()); stBuilder.append("?GoogleAccessId=").append(credentials.getAccount()); @@ -556,7 +555,7 @@ private URL signUrlOptions(String url, BlobInfo blobInfo, long duration, TimeUni } catch (MalformedURLException | UnsupportedEncodingException ex) { throw new IllegalStateException(ex); - } + } } /** diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java index bb0ecc9a14bc..3565df009e33 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/StorageImplTest.java @@ -1645,7 +1645,7 @@ public void testSignUrlWithCustomUrl() ServiceAccountCredentials credentials = new ServiceAccountCredentials(null, ACCOUNT, privateKey, null, null); storage = options.toBuilder().setCredentials(credentials).build().getService(); - URL url = storage.signUrl("https://custom.host.com",BLOB_INFO1, 14, TimeUnit.DAYS); + URL url = storage.signUrl(BLOB_INFO1, 14, TimeUnit.DAYS, Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String stringUrl = url.toString(); String expectedUrl = new StringBuilder("https://custom.host.com/") @@ -1730,7 +1730,7 @@ public void testSignUrlLeadingSlashWithCustomUrl() new ServiceAccountCredentials(null, ACCOUNT, privateKey, null, null); storage = options.toBuilder().setCredentials(credentials).build().getService(); URL url = - storage.signUrl("https://custom.host.com",BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS); + storage.signUrl(BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS, Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String escapedBlobName = UrlEscapers.urlFragmentEscaper().escape(blobName); String stringUrl = url.toString(); String expectedUrl = @@ -1824,13 +1824,13 @@ public void testSignUrlWithOptionsAndCustomUrl() storage = options.toBuilder().setCredentials(credentials).build().getService(); URL url = storage.signUrl( - "https://custom.host.com", BLOB_INFO1, 14, TimeUnit.DAYS, Storage.SignUrlOption.httpMethod(HttpMethod.POST), Storage.SignUrlOption.withContentType(), - Storage.SignUrlOption.withMd5()); + Storage.SignUrlOption.withMd5(), + Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String stringUrl = url.toString(); String expectedUrl = new StringBuilder("https://custom.host.com/") @@ -1937,7 +1937,7 @@ public void testSignUrlForBlobWithSpecialCharsAndCustomUrl() for (char specialChar : specialChars) { String blobName = "/a" + specialChar + "b"; URL url = - storage.signUrl("https://custom.host.com",BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS); + storage.signUrl(BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS, Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String escapedBlobName = UrlEscapers.urlFragmentEscaper().escape(blobName).replace("?", "%3F"); String stringUrl = url.toString(); @@ -2041,13 +2041,13 @@ public void testSignUrlWithExtHeadersAndCustomUrl() extHeaders.put("x-goog-meta-owner", "myself"); URL url = storage.signUrl( - "https://custom.host.com", BLOB_INFO1, 14, TimeUnit.DAYS, Storage.SignUrlOption.httpMethod(HttpMethod.PUT), Storage.SignUrlOption.withContentType(), - Storage.SignUrlOption.withExtHeaders(extHeaders)); + Storage.SignUrlOption.withExtHeaders(extHeaders), + Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String stringUrl = url.toString(); String expectedUrl = new StringBuilder("https://custom.host.com/") @@ -2140,7 +2140,7 @@ public void testSignUrlForBlobWithSlashesAndCustomUrl() String blobName = "/foo/bar/baz #%20other cool stuff.txt"; URL url = - storage.signUrl("https://custom.host.com", BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS); + storage.signUrl(BlobInfo.newBuilder(BUCKET_NAME1, blobName).build(), 14, TimeUnit.DAYS, Storage.SignUrlOption.withExtHostName("https://custom.host.com")); String escapedBlobName = UrlEscapers.urlFragmentEscaper().escape(blobName); String stringUrl = url.toString(); String expectedUrl =