diff --git a/.kokoro/continuous/storage-it.cfg b/.kokoro/continuous/storage-it.cfg index cf9cc474499a..0bb5ed85f911 100644 --- a/.kokoro/continuous/storage-it.cfg +++ b/.kokoro/continuous/storage-it.cfg @@ -25,3 +25,8 @@ env_vars: { key: "GOOGLE_APPLICATION_CREDENTIALS" value: "keystore/73713_java_it_service_account" } + +env_vars: { + key: "IT_SERVICE_ACCOUNT_EMAIL" + value: "it-service-account@gcloud-devel.iam.gserviceaccount.com" +} \ No newline at end of file diff --git a/.kokoro/nightly/storage-it.cfg b/.kokoro/nightly/storage-it.cfg index cf9cc474499a..0bb5ed85f911 100644 --- a/.kokoro/nightly/storage-it.cfg +++ b/.kokoro/nightly/storage-it.cfg @@ -25,3 +25,8 @@ env_vars: { key: "GOOGLE_APPLICATION_CREDENTIALS" value: "keystore/73713_java_it_service_account" } + +env_vars: { + key: "IT_SERVICE_ACCOUNT_EMAIL" + value: "it-service-account@gcloud-devel.iam.gserviceaccount.com" +} \ No newline at end of file diff --git a/.kokoro/presubmit/storage-it.cfg b/.kokoro/presubmit/storage-it.cfg index cf9cc474499a..0bb5ed85f911 100644 --- a/.kokoro/presubmit/storage-it.cfg +++ b/.kokoro/presubmit/storage-it.cfg @@ -25,3 +25,8 @@ env_vars: { key: "GOOGLE_APPLICATION_CREDENTIALS" value: "keystore/73713_java_it_service_account" } + +env_vars: { + key: "IT_SERVICE_ACCOUNT_EMAIL" + value: "it-service-account@gcloud-devel.iam.gserviceaccount.com" +} \ No newline at end of file diff --git a/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java b/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java index ee18735425f0..f686ae3de70a 100644 --- a/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java +++ b/google-cloud-clients/google-cloud-contrib/google-cloud-nio/src/main/java/com/google/cloud/storage/contrib/nio/testing/FakeStorageRpc.java @@ -18,6 +18,8 @@ import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.BucketAccessControl; +import com.google.api.services.storage.model.HmacKey; +import com.google.api.services.storage.model.HmacKeyMetadata; import com.google.api.services.storage.model.Notification; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.Policy; @@ -67,6 +69,7 @@ *
  • checksums, etags *
  • IAM operations *
  • BucketLock operations + *
  • HMAC key operations * * */ @@ -420,6 +423,31 @@ public List listAcls(String bucket, Map options) throw new UnsupportedOperationException(); } + @Override + public HmacKey createHmacKey(String serviceAccountEmail, Map options) { + throw new UnsupportedOperationException(); + } + + @Override + public Tuple> listHmacKeys(Map options) { + throw new UnsupportedOperationException(); + } + + @Override + public HmacKeyMetadata updateHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options) { + throw new UnsupportedOperationException(); + } + + @Override + public HmacKeyMetadata getHmacKey(String accessId, Map options) { + throw new UnsupportedOperationException(); + } + + @Override + public void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options) { + throw new UnsupportedOperationException(); + } + @Override public ObjectAccessControl getDefaultAcl(String bucket, String entity) { throw new UnsupportedOperationException(); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java new file mode 100644 index 000000000000..c133144675e5 --- /dev/null +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/HmacKey.java @@ -0,0 +1,331 @@ +/* + * Copyright 2019 Google LLC + * + * 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 com.google.api.client.util.DateTime; +import java.io.Serializable; +import java.util.Objects; + +/** HMAC key for a service account. */ +public class HmacKey implements Serializable { + + private static final long serialVersionUID = -1809610424373783062L; + private final String secretKey; + private final HmacKeyMetadata metadata; + + private HmacKey(Builder builder) { + this.secretKey = builder.secretKey; + this.metadata = builder.metadata; + } + + public static Builder newBuilder(String secretKey) { + return new Builder(secretKey); + } + + /** Builder for {@code HmacKey} objects. * */ + public static class Builder { + private String secretKey; + private HmacKeyMetadata metadata; + + private Builder(String secretKey) { + this.secretKey = secretKey; + } + + public Builder setSecretKey(String secretKey) { + this.secretKey = secretKey; + return this; + } + + public Builder setMetadata(HmacKeyMetadata metadata) { + this.metadata = metadata; + return this; + } + + /** Creates an {@code HmacKey} object from this builder. * */ + public HmacKey build() { + return new HmacKey(this); + } + } + + /** Returns the secret key associated with this HMAC key. * */ + public String getSecretKey() { + return secretKey; + } + + /** Returns the metadata associated with this HMAC key. * */ + public HmacKeyMetadata getMetadata() { + return metadata; + } + + @Override + public int hashCode() { + return Objects.hash(secretKey, metadata); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HmacKeyMetadata other = (HmacKeyMetadata) obj; + return Objects.equals(this.secretKey, secretKey) && Objects.equals(this.metadata, metadata); + } + + com.google.api.services.storage.model.HmacKey toPb() { + com.google.api.services.storage.model.HmacKey hmacKey = + new com.google.api.services.storage.model.HmacKey(); + hmacKey.setSecret(this.secretKey); + + if (metadata != null) { + hmacKey.setMetadata(metadata.toPb()); + } + + return hmacKey; + } + + static HmacKey fromPb(com.google.api.services.storage.model.HmacKey hmacKey) { + return HmacKey.newBuilder(hmacKey.getSecret()) + .setMetadata(HmacKeyMetadata.fromPb(hmacKey.getMetadata())) + .build(); + } + + public enum HmacKeyState { + ACTIVE("ACTIVE"), + INACTIVE("INACTIVE"), + DELETED("DELETED"); + + private final String state; + + HmacKeyState(String state) { + this.state = state; + } + } + + /** + * The metadata for a service account HMAC key. This class holds all data associated with an HMAC + * key other than the secret key. + */ + public static class HmacKeyMetadata implements Serializable { + + private static final long serialVersionUID = 4571684785352640737L; + private final String accessId; + private final String etag; + private final String id; + private final String projectId; + private final ServiceAccount serviceAccount; + private final HmacKeyState state; + private final Long createTime; + private final Long updateTime; + + private HmacKeyMetadata(Builder builder) { + this.accessId = builder.accessId; + this.etag = builder.etag; + this.id = builder.id; + this.projectId = builder.projectId; + this.serviceAccount = builder.serviceAccount; + this.state = builder.state; + this.createTime = builder.createTime; + this.updateTime = builder.updateTime; + } + + public static Builder newBuilder(ServiceAccount serviceAccount) { + return new Builder(serviceAccount); + } + + public Builder toBuilder() { + return new Builder(this); + } + + public static HmacKeyMetadata of( + ServiceAccount serviceAccount, String accessId, String projectId) { + return newBuilder(serviceAccount).setAccessId(accessId).setProjectId(projectId).build(); + } + + @Override + public int hashCode() { + return Objects.hash(accessId, projectId); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final HmacKeyMetadata other = (HmacKeyMetadata) obj; + return Objects.equals(this.accessId, other.accessId) + && Objects.equals(this.etag, other.etag) + && Objects.equals(this.id, other.id) + && Objects.equals(this.projectId, other.projectId) + && Objects.equals(this.serviceAccount, other.serviceAccount) + && Objects.equals(this.state, other.state) + && Objects.equals(this.createTime, other.createTime) + && Objects.equals(this.updateTime, other.updateTime); + } + + public com.google.api.services.storage.model.HmacKeyMetadata toPb() { + com.google.api.services.storage.model.HmacKeyMetadata metadata = + new com.google.api.services.storage.model.HmacKeyMetadata(); + metadata.setAccessId(this.accessId); + metadata.setEtag(this.etag); + metadata.setId(this.id); + metadata.setProjectId(this.projectId); + metadata.setServiceAccountEmail( + this.serviceAccount == null ? null : this.serviceAccount.getEmail()); + metadata.setState(this.state == null ? null : this.state.toString()); + metadata.setTimeCreated(this.createTime == null ? null : new DateTime(this.createTime)); + metadata.setUpdated(this.updateTime == null ? null : new DateTime(this.updateTime)); + + return metadata; + } + + static HmacKeyMetadata fromPb(com.google.api.services.storage.model.HmacKeyMetadata metadata) { + return newBuilder(ServiceAccount.of(metadata.getServiceAccountEmail())) + .setAccessId(metadata.getAccessId()) + .setCreateTime(metadata.getTimeCreated().getValue()) + .setEtag(metadata.getEtag()) + .setId(metadata.getId()) + .setProjectId(metadata.getProjectId()) + .setState(HmacKeyState.valueOf(metadata.getState())) + .setUpdateTime(metadata.getUpdated().getValue()) + .build(); + } + + /** + * Returns the access id for this HMAC key. This is the id needed to get or delete the key. * + */ + public String getAccessId() { + return accessId; + } + + /** + * Returns HTTP 1.1 Entity tag for this HMAC key. + * + * @see Entity Tags + */ + public String getEtag() { + return etag; + } + + /** Returns the resource name of this HMAC key. * */ + public String getId() { + return id; + } + + /** Returns the project id associated with this HMAC key. * */ + public String getProjectId() { + return projectId; + } + + /** Returns the service account associated with this HMAC key. * */ + public ServiceAccount getServiceAccount() { + return serviceAccount; + } + + /** Returns the current state of this HMAC key. * */ + public HmacKeyState getState() { + return state; + } + + /** Returns the creation time of this HMAC key. * */ + public Long getCreateTime() { + return createTime; + } + + /** Returns the last updated time of this HMAC key. * */ + public Long getUpdateTime() { + return updateTime; + } + + /** Builder for {@code HmacKeyMetadata} objects. * */ + public static class Builder { + private String accessId; + private String etag; + private String id; + private String projectId; + private ServiceAccount serviceAccount; + private HmacKeyState state; + private Long createTime; + private Long updateTime; + + private Builder(ServiceAccount serviceAccount) { + this.serviceAccount = serviceAccount; + } + + private Builder(HmacKeyMetadata metadata) { + this.accessId = metadata.accessId; + this.etag = metadata.etag; + this.id = metadata.id; + this.projectId = metadata.projectId; + this.serviceAccount = metadata.serviceAccount; + this.state = metadata.state; + this.createTime = metadata.createTime; + this.updateTime = metadata.updateTime; + } + + public Builder setAccessId(String accessId) { + this.accessId = accessId; + return this; + } + + public Builder setEtag(String etag) { + this.etag = etag; + return this; + } + + public Builder setId(String id) { + this.id = id; + return this; + } + + public Builder setServiceAccount(ServiceAccount serviceAccount) { + this.serviceAccount = serviceAccount; + return this; + } + + public Builder setState(HmacKeyState state) { + this.state = state; + return this; + } + + public Builder setCreateTime(long createTime) { + this.createTime = createTime; + return this; + } + + public Builder setProjectId(String projectId) { + this.projectId = projectId; + return this; + } + + /** Creates an {@code HmacKeyMetadata} object from this builder. * */ + public HmacKeyMetadata build() { + return new HmacKeyMetadata(this); + } + + public Builder setUpdateTime(long updateTime) { + this.updateTime = updateTime; + return this; + } + } + } +} 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 75f2d7ca1811..bf028bb6726a 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 @@ -30,6 +30,7 @@ import com.google.cloud.Tuple; import com.google.cloud.WriteChannel; import com.google.cloud.storage.Acl.Entity; +import com.google.cloud.storage.HmacKey.HmacKeyMetadata; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @@ -253,6 +254,132 @@ public static BucketSourceOption userProject(String userProject) { } } + /** Class for specifying listHmacKeys options */ + class ListHmacKeysOption extends Option { + private ListHmacKeysOption(StorageRpc.Option rpcOption, Object value) { + super(rpcOption, value); + } + + /** + * Returns an option for the Service Account whose keys to list. If this option is not used, + * keys for all accounts will be listed. + */ + public static ListHmacKeysOption serviceAccount(ServiceAccount serviceAccount) { + return new ListHmacKeysOption( + StorageRpc.Option.SERVICE_ACCOUNT_EMAIL, serviceAccount.getEmail()); + } + + /** Returns an option for the maximum amount of HMAC keys returned per page. */ + public static ListHmacKeysOption maxResults(long pageSize) { + return new ListHmacKeysOption(StorageRpc.Option.MAX_RESULTS, pageSize); + } + + /** Returns an option to specify the page token from which to start listing HMAC keys. */ + public static ListHmacKeysOption pageToken(String pageToken) { + return new ListHmacKeysOption(StorageRpc.Option.PAGE_TOKEN, pageToken); + } + + /** + * Returns an option to specify whether to show deleted keys in the result. This option is false + * by default. + */ + public static ListHmacKeysOption showDeletedKeys(boolean showDeletedKeys) { + return new ListHmacKeysOption(StorageRpc.Option.SHOW_DELETED_KEYS, showDeletedKeys); + } + + /** + * Returns an option to specify the project to be billed for this request. Required for + * Requester Pays buckets. + */ + public static ListHmacKeysOption userProject(String userProject) { + return new ListHmacKeysOption(StorageRpc.Option.USER_PROJECT, userProject); + } + + /** + * Returns an option to specify the Project ID for this request. If not specified, defaults to + * Application Default Credentials. + */ + public static ListHmacKeysOption projectId(String projectId) { + return new ListHmacKeysOption(StorageRpc.Option.PROJECT_ID, projectId); + } + } + + /** Class for specifying createHmacKey options */ + class CreateHmacKeyOption extends Option { + private CreateHmacKeyOption(StorageRpc.Option rpcOption, Object value) { + super(rpcOption, value); + } + + /** + * Returns an option to specify the project to be billed for this request. Required for + * Requester Pays buckets. + */ + public static CreateHmacKeyOption userProject(String userProject) { + return new CreateHmacKeyOption(StorageRpc.Option.USER_PROJECT, userProject); + } + + /** + * Returns an option to specify the Project ID for this request. If not specified, defaults to + * Application Default Credentials. + */ + public static CreateHmacKeyOption projectId(String projectId) { + return new CreateHmacKeyOption(StorageRpc.Option.PROJECT_ID, projectId); + } + } + + /** Class for specifying getHmacKey options */ + class GetHmacKeyOption extends Option { + private GetHmacKeyOption(StorageRpc.Option rpcOption, Object value) { + super(rpcOption, value); + } + + /** + * Returns an option to specify the project to be billed for this request. Required for + * Requester Pays buckets. + */ + public static GetHmacKeyOption userProject(String userProject) { + return new GetHmacKeyOption(StorageRpc.Option.USER_PROJECT, userProject); + } + + /** + * Returns an option to specify the Project ID for this request. If not specified, defaults to + * Application Default Credentials. + */ + public static GetHmacKeyOption projectId(String projectId) { + return new GetHmacKeyOption(StorageRpc.Option.PROJECT_ID, projectId); + } + } + + /** Class for specifying deleteHmacKey options */ + class DeleteHmacKeyOption extends Option { + private DeleteHmacKeyOption(StorageRpc.Option rpcOption, Object value) { + super(rpcOption, value); + } + + /** + * Returns an option to specify the project to be billed for this request. Required for + * Requester Pays buckets. + */ + public static DeleteHmacKeyOption userProject(String userProject) { + return new DeleteHmacKeyOption(StorageRpc.Option.USER_PROJECT, userProject); + } + } + + /** Class for specifying updateHmacKey options */ + class UpdateHmacKeyOption extends Option { + private UpdateHmacKeyOption(StorageRpc.Option rpcOption, Object value) { + super(rpcOption, value); + } + + /** + * Returns an option to specify the project to be billed for this request. Required for + * Requester Pays buckets. + */ + public static UpdateHmacKeyOption userProject(String userProject) { + return new UpdateHmacKeyOption(StorageRpc.Option.USER_PROJECT, userProject); + } + } + /** Class for specifying bucket get options. */ class BucketGetOption extends Option { @@ -2708,6 +2835,112 @@ Blob create( */ List listAcls(BlobId blob); + /** + * Creates a new HMAC Key for the provided service account, including the secret key. Note that + * the secret key is only returned upon creation via this method. + * + *

    Example of creating a new HMAC Key. + * + *

    {@code
    +   * ServiceAccount serviceAccount = ServiceAccount.of("my-service-account@google.com");
    +   *
    +   * HmacKey hmacKey = storage.createHmacKey(serviceAccount);
    +   *
    +   * String secretKey = hmacKey.getSecretKey();
    +   * HmacKey.HmacKeyMetadata metadata = hmacKey.getMetadata();
    +   * }
    + * + * @throws StorageException upon failure + */ + HmacKey createHmacKey(ServiceAccount serviceAccount, CreateHmacKeyOption... options); + + /** + * Lists HMAC keys for a given service account. Note this returns {@code HmacKeyMetadata} objects, + * which do not contain secret keys. + * + *

    Example of listing HMAC keys, specifying project id. + * + *

    {@code
    +   * Page metadataPage = storage.listHmacKeys(
    +   *     Storage.ListHmacKeysOption.projectId("my-project-id"));
    +   * for (HmacKey.HmacKeyMetadata hmacKeyMetadata : metadataPage.getValues()) {
    +   *     //do something with the metadata
    +   * }
    +   * }
    + * + *

    Example of listing HMAC keys, specifying max results and showDeletedKeys. Since projectId is + * not specified, the same project ID as the storage client instance will be used + * + *

    {@code
    +   * ServiceAccount serviceAccount = ServiceAccount.of("my-service-account@google.com");
    +   *
    +   * Page metadataPage = storage.listHmacKeys(
    +   *     Storage.ListHmacKeysOption.serviceAccount(serviceAccount),
    +   *     Storage.ListHmacKeysOption.maxResults(10L),
    +   *     Storage.ListHmacKeysOption.showDeletedKeys(true));
    +   * for (HmacKey.HmacKeyMetadata hmacKeyMetadata : metadataPage.getValues()) {
    +   *     //do something with the metadata
    +   * }
    +   * }
    + * + * @param options the options to apply to this operation + * @throws StorageException upon failure + */ + Page listHmacKeys(ListHmacKeysOption... options); + + /** + * Gets an HMAC key given its access id. Note that this returns a {@code HmacKeyMetadata} object, + * which does not contain the secret key. + * + *

    Example of getting an HMAC key. Since projectId isn't specified, the same project ID as the + * storage client instance will be used. + * + *

    {@code
    +   * String hmacKeyAccessId = "my-access-id";
    +   * HmacKey.HmackeyMetadata hmacKeyMetadata = storage.getHmacKey(hmacKeyAccessId);
    +   * }
    + * + * @throws StorageException upon failure + */ + HmacKeyMetadata getHmacKey(String accessId, GetHmacKeyOption... options); + + /** + * Deletes an HMAC key. Note that only an {@code INACTIVE} key can be deleted. Attempting to + * delete a key whose {@code HmacKey.HmacKeyState} is anything other than {@code INACTIVE} will + * fail. + * + *

    Example of updating an HMAC key's state to INACTIVE and then deleting it. + * + *

    {@code
    +   * String hmacKeyAccessId = "my-access-id";
    +   * HmacKey.HmacKeyMetadata hmacKeyMetadata = storage.getHmacKey(hmacKeyAccessId);
    +   *
    +   * storage.updateHmacKeyState(hmacKeyMetadata, HmacKey.HmacKeyState.INACTIVE);
    +   * storage.deleteHmacKey(hmacKeyMetadata);
    +   * }
    + * + * @throws StorageException upon failure + */ + void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, DeleteHmacKeyOption... options); + + /** + * Updates the state of an HMAC key and returns the updated metadata. + * + *

    Example of updating the state of an HMAC key. + * + *

    {@code
    +   * String hmacKeyAccessId = "my-access-id";
    +   * HmacKey.HmacKeyMetadata hmacKeyMetadata = storage.getHmacKey(hmacKeyAccessId);
    +   *
    +   * storage.updateHmacKeyState(hmacKeyMetadata, HmacKey.HmacKeyState.INACTIVE);
    +   * }
    + * + * @throws StorageException upon failure + */ + HmacKeyMetadata updateHmacKeyState( + final HmacKeyMetadata hmacKeyMetadata, + final HmacKey.HmacKeyState state, + UpdateHmacKeyOption... options); /** * Gets the IAM policy for the provided bucket. * 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 6f127ac5fb0c..33eab5dfd9ab 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 @@ -48,6 +48,7 @@ import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.Tuple; import com.google.cloud.storage.Acl.Entity; +import com.google.cloud.storage.HmacKey.HmacKeyMetadata; import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.RewriteResponse; import com.google.common.base.Function; @@ -297,6 +298,23 @@ public Page getNextPage() { } } + private static class HmacKeyMetadataPageFetcher implements NextPageFetcher { + + private static final long serialVersionUID = 308012320541700881L; + private final StorageOptions serviceOptions; + private final Map options; + + HmacKeyMetadataPageFetcher(StorageOptions serviceOptions, Map options) { + this.serviceOptions = serviceOptions; + this.options = options; + } + + @Override + public Page getNextPage() { + return listHmacKeys(serviceOptions, options); + } + } + @Override public Page list(BucketListOption... options) { return listBuckets(getOptions(), optionMap(options)); @@ -1163,6 +1181,140 @@ public List call() { } } + public HmacKey createHmacKey( + final ServiceAccount serviceAccount, final CreateHmacKeyOption... options) { + try { + return HmacKey.fromPb( + runWithRetries( + new Callable() { + @Override + public com.google.api.services.storage.model.HmacKey call() { + return storageRpc.createHmacKey(serviceAccount.getEmail(), optionMap(options)); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock())); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + @Override + public Page listHmacKeys(ListHmacKeysOption... options) { + return listHmacKeys(getOptions(), optionMap(options)); + } + + @Override + public HmacKeyMetadata getHmacKey(final String accessId, final GetHmacKeyOption... options) { + try { + return HmacKeyMetadata.fromPb( + runWithRetries( + new Callable() { + @Override + public com.google.api.services.storage.model.HmacKeyMetadata call() { + return storageRpc.getHmacKey(accessId, optionMap(options)); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock())); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + private HmacKeyMetadata updateHmacKey( + final HmacKeyMetadata hmacKeyMetadata, final UpdateHmacKeyOption... options) { + try { + return HmacKeyMetadata.fromPb( + runWithRetries( + new Callable() { + @Override + public com.google.api.services.storage.model.HmacKeyMetadata call() { + return storageRpc.updateHmacKey(hmacKeyMetadata.toPb(), optionMap(options)); + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock())); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + @Override + public HmacKeyMetadata updateHmacKeyState( + final HmacKeyMetadata hmacKeyMetadata, + final HmacKey.HmacKeyState state, + final UpdateHmacKeyOption... options) { + HmacKeyMetadata updatedMetadata = + HmacKeyMetadata.newBuilder(hmacKeyMetadata.getServiceAccount()) + .setProjectId(hmacKeyMetadata.getProjectId()) + .setAccessId(hmacKeyMetadata.getAccessId()) + .setState(state) + .build(); + return updateHmacKey(updatedMetadata, options); + } + + @Override + public void deleteHmacKey(final HmacKeyMetadata metadata, final DeleteHmacKeyOption... options) { + try { + runWithRetries( + new Callable() { + @Override + public Void call() { + storageRpc.deleteHmacKey(metadata.toPb(), optionMap(options)); + return null; + } + }, + getOptions().getRetrySettings(), + EXCEPTION_HANDLER, + getOptions().getClock()); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + private static Page listHmacKeys( + final StorageOptions serviceOptions, final Map options) { + try { + Tuple> result = + runWithRetries( + new Callable< + Tuple< + String, Iterable>>() { + @Override + public Tuple< + String, Iterable> + call() { + return serviceOptions.getStorageRpcV1().listHmacKeys(options); + } + }, + serviceOptions.getRetrySettings(), + EXCEPTION_HANDLER, + serviceOptions.getClock()); + String cursor = result.x(); + final Iterable metadata = + result.y() == null + ? ImmutableList.of() + : Iterables.transform( + result.y(), + new Function< + com.google.api.services.storage.model.HmacKeyMetadata, HmacKeyMetadata>() { + @Override + public HmacKeyMetadata apply( + com.google.api.services.storage.model.HmacKeyMetadata metadataPb) { + return HmacKeyMetadata.fromPb(metadataPb); + } + }); + return new PageImpl<>( + new HmacKeyMetadataPageFetcher(serviceOptions, options), cursor, metadata); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + @Override public Policy getIamPolicy(final String bucket, BucketSourceOption... options) { try { diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index d162b33c5c7f..847bb3374ec4 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -46,6 +46,9 @@ import com.google.api.services.storage.model.Buckets; import com.google.api.services.storage.model.ComposeRequest; import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions; +import com.google.api.services.storage.model.HmacKey; +import com.google.api.services.storage.model.HmacKeyMetadata; +import com.google.api.services.storage.model.HmacKeysMetadata; import com.google.api.services.storage.model.Notification; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.Objects; @@ -1231,6 +1234,132 @@ public List listAcls(String bucket, String object, Long gen } } + @Override + public HmacKey createHmacKey(String serviceAccountEmail, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_CREATE_HMAC_KEY); + Scope scope = tracer.withSpan(span); + String projectId = Option.PROJECT_ID.getString(options); + if (projectId == null) { + projectId = this.options.getProjectId(); + } + try { + return storage + .projects() + .hmacKeys() + .create(projectId, serviceAccountEmail) + .setUserProject(Option.USER_PROJECT.getString(options)) + .execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + } + + @Override + public Tuple> listHmacKeys(Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_LIST_HMAC_KEYS); + Scope scope = tracer.withSpan(span); + String projectId = Option.PROJECT_ID.getString(options); + if (projectId == null) { + projectId = this.options.getProjectId(); + } + try { + HmacKeysMetadata hmacKeysMetadata = + storage + .projects() + .hmacKeys() + .list(projectId) + .setServiceAccountEmail(Option.SERVICE_ACCOUNT_EMAIL.getString(options)) + .setPageToken(Option.PAGE_TOKEN.getString(options)) + .setMaxResults(Option.MAX_RESULTS.getLong(options)) + .setShowDeletedKeys(Option.SHOW_DELETED_KEYS.getBoolean(options)) + .execute(); + return Tuple.>of( + hmacKeysMetadata.getNextPageToken(), hmacKeysMetadata.getItems()); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + } + + @Override + public HmacKeyMetadata getHmacKey(String accessId, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_HMAC_KEY); + Scope scope = tracer.withSpan(span); + String projectId = Option.PROJECT_ID.getString(options); + if (projectId == null) { + projectId = this.options.getProjectId(); + } + try { + return storage + .projects() + .hmacKeys() + .get(projectId, accessId) + .setUserProject(Option.USER_PROJECT.getString(options)) + .execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + } + + @Override + public HmacKeyMetadata updateHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_UPDATE_HMAC_KEY); + Scope scope = tracer.withSpan(span); + String projectId = hmacKeyMetadata.getProjectId(); + if (projectId == null) { + projectId = this.options.getProjectId(); + } + try { + return storage + .projects() + .hmacKeys() + .update(projectId, hmacKeyMetadata.getAccessId(), hmacKeyMetadata) + .setUserProject(Option.USER_PROJECT.getString(options)) + .execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + } + + @Override + public void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options) { + Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_DELETE_HMAC_KEY); + Scope scope = tracer.withSpan(span); + String projectId = hmacKeyMetadata.getProjectId(); + if (projectId == null) { + projectId = this.options.getProjectId(); + } + try { + storage + .projects() + .hmacKeys() + .delete(projectId, hmacKeyMetadata.getAccessId()) + .setUserProject(Option.USER_PROJECT.getString(options)) + .execute(); + } catch (IOException ex) { + span.setStatus(Status.UNKNOWN.withDescription(ex.getMessage())); + throw translate(ex); + } finally { + scope.close(); + span.end(); + } + } + @Override public Policy getIamPolicy(String bucket, Map options) { Span span = startSpan(HttpStorageRpcSpans.SPAN_NAME_GET_BUCKET_IAM_POLICY); diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java index 490542f861cb..ff3be9c07262 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpcSpans.java @@ -72,6 +72,13 @@ class HttpStorageRpcSpans { static final String SPAN_NAME_PATCH_OBJECT_ACL = getTraceSpanName("patchAcl(ObjectAccessControl)"); static final String SPAN_NAME_LIST_OBJECT_ACLS = getTraceSpanName("listAcls(String,String,Long)"); + static final String SPAN_NAME_CREATE_HMAC_KEY = getTraceSpanName("createHmacKey(String)"); + static final String SPAN_NAME_GET_HMAC_KEY = getTraceSpanName("getHmacKey(String)"); + static final String SPAN_NAME_DELETE_HMAC_KEY = getTraceSpanName("deleteHmacKey(String)"); + static final String SPAN_NAME_LIST_HMAC_KEYS = + getTraceSpanName("listHmacKeys(String,String,Long)"); + static final String SPAN_NAME_UPDATE_HMAC_KEY = + getTraceSpanName("updateHmacKey(HmacKeyMetadata)"); static final String SPAN_NAME_GET_BUCKET_IAM_POLICY = getTraceSpanName("getIamPolicy(String,Map)"); static final String SPAN_NAME_SET_BUCKET_IAM_POLICY = diff --git a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java index c85958176d69..da922b78da14 100644 --- a/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java +++ b/google-cloud-clients/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java @@ -19,6 +19,8 @@ import com.google.api.core.InternalApi; import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.BucketAccessControl; +import com.google.api.services.storage.model.HmacKey; +import com.google.api.services.storage.model.HmacKeyMetadata; import com.google.api.services.storage.model.Notification; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.Policy; @@ -50,6 +52,7 @@ enum Option { IF_SOURCE_GENERATION_NOT_MATCH("ifSourceGenerationNotMatch"), IF_DISABLE_GZIP_CONTENT("disableGzipContent"), PREFIX("prefix"), + PROJECT_ID("projectId"), PROJECTION("projection"), MAX_RESULTS("maxResults"), PAGE_TOKEN("pageToken"), @@ -58,7 +61,9 @@ enum Option { FIELDS("fields"), CUSTOMER_SUPPLIED_KEY("customerSuppliedKey"), USER_PROJECT("userProject"), - KMS_KEY_NAME("kmsKeyName"); + KMS_KEY_NAME("kmsKeyName"), + SERVICE_ACCOUNT_EMAIL("serviceAccount"), + SHOW_DELETED_KEYS("showDeletedKeys"); private final String value; @@ -434,6 +439,42 @@ void write( */ List listAcls(String bucket, String object, Long generation); + /** + * Creates a new HMAC key for the provided service account email. + * + * @throws StorageException upon failure + */ + HmacKey createHmacKey(String serviceAccountEmail, Map options); + + /** + * Lists the HMAC keys for the provided service account email. + * + * @throws StorageException upon failure + */ + Tuple> listHmacKeys(Map options); + + /** + * Updates an HMAC key for the provided metadata object and returns the updated object. Only + * updates the State field. + * + * @throws StorageException upon failure + */ + HmacKeyMetadata updateHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options); + + /** + * Returns the HMAC key associated with the provided access id. + * + * @throws StorageException upon failure + */ + HmacKeyMetadata getHmacKey(String accessId, Map options); + + /** + * Deletes the HMAC key associated with the provided metadata object. + * + * @throws StorageException upon failure + */ + void deleteHmacKey(HmacKeyMetadata hmacKeyMetadata, Map options); + /** * Returns the IAM policy for the specified bucket. * diff --git a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 8b2d627b23db..9e3a09f3dcc3 100644 --- a/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-clients/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -64,6 +64,7 @@ import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleAction; import com.google.cloud.storage.BucketInfo.LifecycleRule.LifecycleCondition; import com.google.cloud.storage.CopyWriter; +import com.google.cloud.storage.HmacKey; import com.google.cloud.storage.HttpMethod; import com.google.cloud.storage.ServiceAccount; import com.google.cloud.storage.Storage; @@ -2108,6 +2109,91 @@ public void testBlobAcl() { } } + @Test + public void testHmacKey() { + ServiceAccount serviceAccount = ServiceAccount.of(System.getenv("IT_SERVICE_ACCOUNT_EMAIL")); + try { + + HmacKey hmacKey = storage.createHmacKey(serviceAccount); + String secretKey = hmacKey.getSecretKey(); + assertNotNull(secretKey); + HmacKey.HmacKeyMetadata metadata = hmacKey.getMetadata(); + String accessId = metadata.getAccessId(); + + assertNotNull(accessId); + assertNotNull(metadata.getEtag()); + assertNotNull(metadata.getId()); + assertEquals(remoteStorageHelper.getOptions().getProjectId(), metadata.getProjectId()); + assertEquals(serviceAccount.getEmail(), metadata.getServiceAccount().getEmail()); + assertEquals(HmacKey.HmacKeyState.ACTIVE, metadata.getState()); + assertNotNull(metadata.getCreateTime()); + assertNotNull(metadata.getUpdateTime()); + + Page metadatas = + storage.listHmacKeys(Storage.ListHmacKeysOption.serviceAccount(serviceAccount)); + boolean createdHmacKeyIsInList = false; + for (HmacKey.HmacKeyMetadata hmacKeyMetadata : metadatas.iterateAll()) { + if (accessId.equals(hmacKeyMetadata.getAccessId())) { + createdHmacKeyIsInList = true; + break; + } + } + + if (!createdHmacKeyIsInList) { + fail("Created an HMAC key but it didn't show up in list()"); + } + + HmacKey.HmacKeyMetadata getResult = storage.getHmacKey(accessId); + assertEquals(metadata, getResult); + + storage.updateHmacKeyState(metadata, HmacKey.HmacKeyState.INACTIVE); + + storage.deleteHmacKey(metadata); + + metadatas = storage.listHmacKeys(Storage.ListHmacKeysOption.serviceAccount(serviceAccount)); + createdHmacKeyIsInList = false; + for (HmacKey.HmacKeyMetadata hmacKeyMetadata : metadatas.iterateAll()) { + if (accessId.equals(hmacKeyMetadata.getAccessId())) { + createdHmacKeyIsInList = true; + break; + } + } + + if (createdHmacKeyIsInList) { + fail("Deleted an HMAC key but it showed up in list()"); + } + + storage.createHmacKey(serviceAccount); + storage.createHmacKey(serviceAccount); + storage.createHmacKey(serviceAccount); + storage.createHmacKey(serviceAccount); + + metadatas = + storage.listHmacKeys( + Storage.ListHmacKeysOption.serviceAccount(serviceAccount), + Storage.ListHmacKeysOption.maxResults(2L)); + + String nextPageToken = metadatas.getNextPageToken(); + + assertEquals(2, Iterators.size(metadatas.getValues().iterator())); + + metadatas = + storage.listHmacKeys( + Storage.ListHmacKeysOption.serviceAccount(serviceAccount), + Storage.ListHmacKeysOption.maxResults(2L), + Storage.ListHmacKeysOption.pageToken(nextPageToken)); + + assertEquals(2, Iterators.size(metadatas.getValues().iterator())); + } finally { + Page metadatas = + storage.listHmacKeys(Storage.ListHmacKeysOption.serviceAccount(serviceAccount)); + for (HmacKey.HmacKeyMetadata hmacKeyMetadata : metadatas.iterateAll()) { + storage.updateHmacKeyState(hmacKeyMetadata, HmacKey.HmacKeyState.INACTIVE); + storage.deleteHmacKey(hmacKeyMetadata); + } + } + } + @Test public void testReadCompressedBlob() throws IOException { String blobName = "test-read-compressed-blob";