diff --git a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/FakeStorageRpc.java b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/FakeStorageRpc.java index 3e7c603cae3f..4ce3abae3dc2 100644 --- a/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/FakeStorageRpc.java +++ b/google-cloud-contrib/google-cloud-nio/src/test/java/com/google/cloud/storage/contrib/nio/FakeStorageRpc.java @@ -19,7 +19,9 @@ import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.BucketAccessControl; import com.google.api.services.storage.model.ObjectAccessControl; +import com.google.api.services.storage.model.Policy; import com.google.api.services.storage.model.StorageObject; +import com.google.api.services.storage.model.TestIamPermissionsResponse; import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageException; import com.google.cloud.storage.spi.v1.RpcBatch; @@ -61,6 +63,7 @@ *
  • continueRewrite *
  • createBatch *
  • checksums, etags + *
  • IAM operations
  • * * */ @@ -443,4 +446,19 @@ private static boolean processedAsFolder(StorageObject so, String delimiter, Str folders.put(folderName, fakeFolder); return true; } + + @Override + public Policy getPolicy(String bucket) { + throw new UnsupportedOperationException(); + } + + @Override + public Policy updatePolicy(String bucket, Policy policy) { + throw new UnsupportedOperationException(); + } + + @Override + public TestIamPermissionsResponse testPermissions(String bucket, List permissions) { + throw new UnsupportedOperationException(); + } } diff --git a/google-cloud-core/src/main/java/com/google/cloud/Policy.java b/google-cloud-core/src/main/java/com/google/cloud/Policy.java index c8fc044086ea..0de5cdc9652c 100644 --- a/google-cloud-core/src/main/java/com/google/cloud/Policy.java +++ b/google-cloud-core/src/main/java/com/google/cloud/Policy.java @@ -202,7 +202,6 @@ public final Builder removeIdentity(Role role, Identity first, Identity... other return this; } - /** * Sets the policy's etag. * @@ -214,7 +213,7 @@ public final Builder removeIdentity(Role role, Identity first, Identity... other * applied to the same version of the policy. If no etag is provided in the call to * setIamPolicy, then the existing policy is overwritten blindly. */ - protected final Builder setEtag(String etag) { + public final Builder setEtag(String etag) { this.etag = etag; return this; } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java new file mode 100644 index 000000000000..3da751a9f367 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java @@ -0,0 +1,62 @@ +/* + * 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.List; +import java.util.Map; +import java.util.Set; + +import com.google.api.services.storage.model.Policy.Bindings; +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; + +/** + * Helper for converting between the Policy model provided by the API and the Policy model provided + * by this library. + */ +class PolicyHelper { + + static Policy convertFromApiPolicy(com.google.api.services.storage.model.Policy apiPolicy) { + Policy.Builder policyBuilder = Policy.newBuilder(); + for (Bindings binding : apiPolicy.getBindings()) { + for (String member : binding.getMembers()) { + policyBuilder.addIdentity(Role.of(binding.getRole()), Identity.valueOf(member)); + } + } + return policyBuilder.setEtag(apiPolicy.getEtag()).build(); + } + + static com.google.api.services.storage.model.Policy convertToApiPolicy(Policy policy) { + List bindings = new ArrayList<>(policy.getBindings().size()); + for (Map.Entry> entry : policy.getBindings().entrySet()) { + List members = new ArrayList<>(entry.getValue().size()); + for (Identity identity : entry.getValue()) { + members.add(identity.strValue()); + } + bindings.add(new Bindings().setMembers(members).setRole(entry.getKey().getValue())); + } + return new com.google.api.services.storage.model.Policy() + .setBindings(bindings) + .setEtag(policy.getEtag()); + } + + private PolicyHelper() { + // Intentionally left blank. + } +} 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 2cf7c1f001bd..65a30add97e8 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 @@ -19,11 +19,25 @@ import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; +import java.io.InputStream; +import java.io.Serializable; +import java.net.URL; +import java.security.Key; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.TimeUnit; + import com.google.auth.ServiceAccountSigner; import com.google.auth.ServiceAccountSigner.SigningException; import com.google.cloud.FieldSelector; import com.google.cloud.FieldSelector.Helper; import com.google.cloud.Page; +import com.google.cloud.Policy; import com.google.cloud.ReadChannel; import com.google.cloud.Service; import com.google.cloud.WriteChannel; @@ -35,19 +49,6 @@ import com.google.common.collect.Lists; import com.google.common.io.BaseEncoding; -import java.io.InputStream; -import java.io.Serializable; -import java.net.URL; -import java.security.Key; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Objects; -import java.util.Set; -import java.util.concurrent.TimeUnit; - /** * An interface for Google Cloud Storage. * @@ -2369,4 +2370,57 @@ public static Builder newBuilder() { * @throws StorageException upon failure */ List listAcls(BlobId blob); + + /** + * Gets the IAM policy for the provided bucket. + * + *

    Example of getting the IAM policy for a bucket. + *

     {@code
    +   * String bucketName = "my_unique_bucket";
    +   * Policy policy = storage.getPolicy(bucketName);
    +   * }
    + * + * @throws StorageException upon failure + */ + Policy getPolicy(String bucket); + + /** + * Updates the IAM policy on the specified bucket. + * + *

    Example of updating the IAM policy on a bucket. + *

    {@code
    +   * // We want to make all objects in our bucket publicly readable.
    +   * String bucketName = "my_unique_bucket";
    +   * Policy currentPolicy = storage.getPolicy(bucketName);
    +   * Policy updatedPolicy =
    +   *     storage.updatePolicy(
    +   *         bucketName,
    +   *         currentPolicy.toBuilder()
    +   *             .addIdentity(StorageRoles.objectViewer(), Identity.allUsers())
    +   *             .build());
    +   * }
    + * + * @throws StorageException upon failure + */ + Policy updatePolicy(String bucket, Policy policy); + + /** + * Tests whether the caller holds the permissions on the specified bucket. Returns a list of + * booleans in the same placement and order in which the permissions were specified. + * + *

    Example of testing permissions on a bucket. + *

     {@code
    +   * String bucketName = "my_unique_bucket";
    +   * List response =
    +   *     storage.testPermissions(
    +   *         bucket,
    +   *         ImmutableList.of("storage.buckets.get", "storage.buckets.getIamPolicy"));
    +   * for (boolean hasPermission : response) {
    +   *   // Do something with permission test response
    +   * }
    +   * }
    + * + * @throws StorageException upon failure + */ + List testPermissions(String bucket, List permissions); } 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 c2ec81e683e9..30a41fd81185 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 @@ -17,6 +17,8 @@ package com.google.cloud.storage; import static com.google.cloud.RetryHelper.runWithRetries; +import static com.google.cloud.storage.PolicyHelper.convertFromApiPolicy; +import static com.google.cloud.storage.PolicyHelper.convertToApiPolicy; import static com.google.cloud.storage.spi.v1.StorageRpc.Option.DELIMITER; import static com.google.cloud.storage.spi.v1.StorageRpc.Option.IF_GENERATION_MATCH; import static com.google.cloud.storage.spi.v1.StorageRpc.Option.IF_GENERATION_NOT_MATCH; @@ -31,15 +33,32 @@ 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.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.services.storage.model.BucketAccessControl; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.StorageObject; +import com.google.api.services.storage.model.TestIamPermissionsResponse; import com.google.auth.ServiceAccountSigner; import com.google.cloud.BaseService; import com.google.cloud.BatchResult; import com.google.cloud.Page; import com.google.cloud.PageImpl; import com.google.cloud.PageImpl.NextPageFetcher; +import com.google.cloud.Policy; import com.google.cloud.ReadChannel; import com.google.cloud.RetryHelper.RetryHelperException; import com.google.cloud.storage.Acl.Entity; @@ -49,6 +68,7 @@ import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; @@ -57,20 +77,6 @@ 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.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.concurrent.Callable; -import java.util.concurrent.TimeUnit; - final class StorageImpl extends BaseService implements Storage { private static final byte[] EMPTY_BYTE_ARRAY = {}; @@ -854,6 +860,58 @@ public List call() { throw StorageException.translateAndThrow(e); } } + + @Override + public Policy getPolicy(final String bucket) { + try { + return convertFromApiPolicy(runWithRetries(new Callable() { + @Override + public com.google.api.services.storage.model.Policy call() { + return storageRpc.getPolicy(bucket); + } + }, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock())); + } catch (RetryHelperException e){ + throw StorageException.translateAndThrow(e); + } + } + + @Override + public Policy updatePolicy(final String bucket, final Policy policy) { + try { + return convertFromApiPolicy(runWithRetries(new Callable() { + @Override + public com.google.api.services.storage.model.Policy call() { + return storageRpc.updatePolicy(bucket, convertToApiPolicy(policy)); + } + }, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock())); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } + + @Override + public List testPermissions(final String bucket, final List permissions) { + try { + TestIamPermissionsResponse response = runWithRetries(new Callable() { + @Override + public TestIamPermissionsResponse call() { + return storageRpc.testPermissions(bucket, permissions); + } + }, getOptions().getRetrySettings(), EXCEPTION_HANDLER, getOptions().getClock()); + final Set heldPermissions = + response.getPermissions() != null + ? ImmutableSet.copyOf(response.getPermissions()) + : ImmutableSet.of(); + return Lists.transform(permissions, new Function() { + @Override + public Boolean apply(String permission) { + return heldPermissions.contains(permission); + } + }); + } catch (RetryHelperException e) { + throw StorageException.translateAndThrow(e); + } + } private static void addToOptionMap(StorageRpc.Option option, T defaultValue, Map map) { diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java index 7d11be03b25a..15981d0ac075 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/HttpStorageRpc.java @@ -48,7 +48,9 @@ import com.google.api.services.storage.model.ComposeRequest.SourceObjects.ObjectPreconditions; import com.google.api.services.storage.model.ObjectAccessControl; import com.google.api.services.storage.model.Objects; +import com.google.api.services.storage.model.Policy; import com.google.api.services.storage.model.StorageObject; +import com.google.api.services.storage.model.TestIamPermissionsResponse; import com.google.cloud.BaseServiceException; import com.google.cloud.HttpTransportOptions; import com.google.cloud.storage.StorageException; @@ -834,4 +836,31 @@ public List listAcls(String bucket, String object, Long gen throw translate(ex); } } + + @Override + public Policy getPolicy(String bucket) { + try { + return storage.buckets().getIamPolicy(bucket).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public Policy updatePolicy(String bucket, Policy policy) { + try { + return storage.buckets().setIamPolicy(bucket, policy).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } + + @Override + public TestIamPermissionsResponse testPermissions(String bucket, List permissions) { + try { + return storage.buckets().testIamPermissions(bucket, permissions).execute(); + } catch (IOException ex) { + throw translate(ex); + } + } } diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java index 91b9601181ea..31a1ca4896aa 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/spi/v1/StorageRpc.java @@ -19,7 +19,9 @@ import com.google.api.services.storage.model.Bucket; import com.google.api.services.storage.model.BucketAccessControl; import com.google.api.services.storage.model.ObjectAccessControl; +import com.google.api.services.storage.model.Policy; import com.google.api.services.storage.model.StorageObject; +import com.google.api.services.storage.model.TestIamPermissionsResponse; import com.google.cloud.ServiceRpc; import com.google.cloud.storage.StorageException; @@ -427,4 +429,25 @@ void write(String uploadId, byte[] toWrite, int toWriteOffset, long destOffset, * @throws StorageException upon failure */ List listAcls(String bucket, String object, Long generation); + + /** + * Returns the IAM policy for the specified bucket. + * + * @throws StorageException upon failure + */ + Policy getPolicy(String bucket); + + /** + * Updates the IAM policy for the specified bucket. + * + * @throws StorageException upon failure + */ + Policy updatePolicy(String bucket, Policy policy); + + /** + * Tests whether the caller holds the specified permissions for the specified bucket. + * + * @throws StorageException upon failure + */ + TestIamPermissionsResponse testPermissions(String bucket, List permissions); } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java new file mode 100644 index 000000000000..70b66f7b19d5 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java @@ -0,0 +1,66 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.google.api.services.storage.model.Policy.Bindings; +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.storage.testing.ApiPolicyMatcher; +import com.google.common.collect.ImmutableList; + +public class PolicyHelperTest { + + private static final String ETAG = "CAE="; + + @Test + public void testEquivalence() { + Policy libPolicy = + Policy.newBuilder() + .addIdentity(StorageRoles.objectViewer(), Identity.allUsers()) + .addIdentity( + StorageRoles.objectAdmin(), + Identity.user("test1@gmail.com"), + Identity.user("test2@gmail.com")) + .setEtag(ETAG) + .build(); + com.google.api.services.storage.model.Policy apiPolicy = + new com.google.api.services.storage.model.Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test1@gmail.com", + "user:test2@gmail.com")) + .setRole("roles/storage.objectAdmin"))) + .setEtag(ETAG); + + Policy actualLibPolicy = PolicyHelper.convertFromApiPolicy(apiPolicy); + com.google.api.services.storage.model.Policy actualApiPolicy = + PolicyHelper.convertToApiPolicy(libPolicy); + + assertEquals(libPolicy, actualLibPolicy); + assertTrue(new ApiPolicyMatcher(apiPolicy).matches(actualApiPolicy)); + } +} 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 397a167503ee..1b72623ee92a 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 @@ -16,6 +16,9 @@ package com.google.cloud.storage; +import static com.google.cloud.storage.testing.ApiPolicyMatcher.eqApiPolicy; +import static org.easymock.EasyMock.cmp; +import static org.easymock.EasyMock.eq; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -25,11 +28,49 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.ByteBuffer; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import javax.crypto.spec.SecretKeySpec; + +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.easymock.LogicalOperator; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + import com.google.api.client.googleapis.json.GoogleJsonError; import com.google.api.gax.core.ApiClock; +import com.google.api.services.storage.model.Policy.Bindings; import com.google.api.services.storage.model.StorageObject; +import com.google.api.services.storage.model.TestIamPermissionsResponse; import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.Identity; import com.google.cloud.Page; +import com.google.cloud.Policy; import com.google.cloud.ReadChannel; import com.google.api.gax.core.RetrySettings; import com.google.cloud.ServiceOptions; @@ -53,39 +94,6 @@ import com.google.common.io.BaseEncoding; import com.google.common.net.UrlEscapers; -import org.easymock.Capture; -import org.easymock.EasyMock; -import org.junit.After; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.net.URL; -import java.net.URLDecoder; -import java.nio.ByteBuffer; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Signature; -import java.security.SignatureException; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import javax.crypto.spec.SecretKeySpec; - public class StorageImplTest { private static final String BUCKET_NAME1 = "b1"; @@ -243,6 +251,33 @@ public class StorageImplTest { private static final Map ENCRYPTION_KEY_OPTIONS = ImmutableMap.of(StorageRpc.Option.CUSTOMER_SUPPLIED_KEY, BASE64_KEY); + // IAM policies + private static final String POLICY_ETAG1 = "CAE="; + private static final String POLICY_ETAG2 = "CAI="; + private static final Policy LIB_POLICY1 = + Policy.newBuilder() + .addIdentity(StorageRoles.objectViewer(), Identity.allUsers()) + .addIdentity( + StorageRoles.objectAdmin(), + Identity.user("test1@gmail.com"), + Identity.user("test2@gmail.com")) + .setEtag(POLICY_ETAG1) + .build(); + + private static final com.google.api.services.storage.model.Policy API_POLICY1 = + new com.google.api.services.storage.model.Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test1@gmail.com", + "user:test2@gmail.com")) + .setRole("roles/storage.objectAdmin"))) + .setEtag(POLICY_ETAG1); + private static final String PRIVATE_KEY_STRING = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoG" + "BAL2xolH1zrISQ8+GzOV29BNjjzq4/HIP8Psd1+cZb81vDklSF+95wB250MSE0BDc81pvIMwj5OmIfLg1NY6uB" @@ -1947,6 +1982,106 @@ public void testListBlobAcl() { assertEquals(ImmutableList.of(ACL, OTHER_ACL), acls); } + @Test + public void testGetPolicy() { + EasyMock.expect(storageRpcMock.getPolicy(BUCKET_NAME1)).andReturn(API_POLICY1); + EasyMock.replay(storageRpcMock); + initializeService(); + assertEquals(LIB_POLICY1, storage.getPolicy(BUCKET_NAME1)); + } + + @Test + public void testUpdatePolicy() { + com.google.api.services.storage.model.Policy preCommitApiPolicy = + new com.google.api.services.storage.model.Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test1@gmail.com", + "user:test2@gmail.com")) + .setRole("roles/storage.objectAdmin"), + new Bindings() + .setMembers(ImmutableList.of("group:test-group@gmail.com")) + .setRole("roles/storage.admin"))) + .setEtag(POLICY_ETAG1); + // postCommitApiPolicy is identical but for the etag, which has been updated. + com.google.api.services.storage.model.Policy postCommitApiPolicy = + new com.google.api.services.storage.model.Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test1@gmail.com", + "user:test2@gmail.com")) + .setRole("roles/storage.objectAdmin"), + new Bindings() + .setMembers(ImmutableList.of("group:test-group@gmail.com")) + .setRole("roles/storage.admin"))) + .setEtag(POLICY_ETAG2); + Policy postCommitLibPolicy = + Policy.newBuilder() + .addIdentity(StorageRoles.objectViewer(), Identity.allUsers()) + .addIdentity( + StorageRoles.objectAdmin(), + Identity.user("test1@gmail.com"), + Identity.user("test2@gmail.com")) + .addIdentity(StorageRoles.admin(), Identity.group("test-group@gmail.com")) + .setEtag(POLICY_ETAG2) + .build(); + + EasyMock.expect(storageRpcMock.getPolicy(BUCKET_NAME1)).andReturn(API_POLICY1); + EasyMock.expect( + storageRpcMock.updatePolicy( + eq(BUCKET_NAME1), + eqApiPolicy(preCommitApiPolicy))) + .andReturn(postCommitApiPolicy); + EasyMock.replay(storageRpcMock); + initializeService(); + + Policy currentPolicy = storage.getPolicy(BUCKET_NAME1); + Policy updatedPolicy = + storage.updatePolicy( + BUCKET_NAME1, + currentPolicy.toBuilder() + .addIdentity(StorageRoles.admin(), Identity.group("test-group@gmail.com")) + .build()); + assertEquals(updatedPolicy, postCommitLibPolicy); + } + + @Test + public void testTestPermissionsNull() { + ImmutableList expectedPermissions = ImmutableList.of(false, false, false); + ImmutableList checkedPermissions = + ImmutableList.of("storage.buckets.get", "storage.buckets.getIamPolicy", "storage.objects.list"); + + EasyMock.expect(storageRpcMock.testPermissions(BUCKET_NAME1, checkedPermissions)) + .andReturn(new TestIamPermissionsResponse()); + EasyMock.replay(storageRpcMock); + initializeService(); + assertEquals(expectedPermissions, storage.testPermissions(BUCKET_NAME1, checkedPermissions)); + } + + @Test + public void testTestPermissionsNonNull() { + ImmutableList expectedPermissions = ImmutableList.of(true, false, true); + ImmutableList checkedPermissions = + ImmutableList.of("storage.buckets.get", "storage.buckets.getIamPolicy", "storage.objects.list"); + + EasyMock.expect(storageRpcMock.testPermissions(BUCKET_NAME1, checkedPermissions)) + .andReturn(new TestIamPermissionsResponse() + .setPermissions(ImmutableList.of("storage.objects.list", "storage.buckets.get"))); + EasyMock.replay(storageRpcMock); + initializeService(); + assertEquals(expectedPermissions, storage.testPermissions(BUCKET_NAME1, checkedPermissions)); + } + @Test public void testRetryableException() { BlobId blob = BlobId.of(BUCKET_NAME1, BLOB_NAME1); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java index 48ae816e8c3d..9215058595a5 100644 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/it/ITStorageTest.java @@ -16,6 +16,7 @@ package com.google.cloud.storage.it; +import static com.google.common.collect.Sets.newHashSet; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; @@ -25,7 +26,38 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.security.Key; +import java.security.NoSuchAlgorithmException; +import java.security.spec.InvalidKeySpecException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.zip.GZIPInputStream; + +import javax.crypto.spec.SecretKeySpec; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.google.cloud.Identity; import com.google.cloud.Page; +import com.google.cloud.Policy; import com.google.cloud.ReadChannel; import com.google.cloud.RestorableState; import com.google.cloud.WriteChannel; @@ -46,6 +78,7 @@ import com.google.cloud.storage.StorageBatchResult; import com.google.cloud.storage.StorageClass; import com.google.cloud.storage.StorageException; +import com.google.cloud.storage.StorageRoles; import com.google.cloud.storage.testing.RemoteStorageHelper; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -56,37 +89,9 @@ import com.google.common.io.BaseEncoding; import com.google.common.io.ByteStreams; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.nio.ByteBuffer; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.spec.InvalidKeySpecException; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.Set; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.zip.GZIPInputStream; - -import javax.crypto.spec.SecretKeySpec; - public class ITStorageTest { + private static RemoteStorageHelper remoteStorageHelper; private static Storage storage; private static final Logger log = Logger.getLogger(ITStorageTest.class.getName()); @@ -104,8 +109,8 @@ public class ITStorageTest { @BeforeClass public static void beforeClass() throws NoSuchAlgorithmException, InvalidKeySpecException { - RemoteStorageHelper helper = RemoteStorageHelper.create(); - storage = helper.getOptions().getService(); + remoteStorageHelper = RemoteStorageHelper.create(); + storage = remoteStorageHelper.getOptions().getService(); storage.create(BucketInfo.of(BUCKET)); } @@ -1440,4 +1445,49 @@ public void testReadCompressedBlob() throws IOException { } blob.delete(); } + + @Test + public void testBucketPolicy() { + String projectId = remoteStorageHelper.getOptions().getProjectId(); + Identity projectOwner = Identity.projectOwner(projectId); + Identity projectEditor = Identity.projectEditor(projectId); + Identity projectViewer = Identity.projectViewer(projectId); + Map> bindingsWithoutPublicRead = + ImmutableMap.of( + StorageRoles.legacyBucketOwner(), (Set) newHashSet(projectOwner, projectEditor), + StorageRoles.legacyBucketReader(), newHashSet(projectViewer)); + Map> bindingsWithPublicRead = + ImmutableMap.of( + StorageRoles.legacyBucketOwner(), (Set) newHashSet(projectOwner, projectEditor), + StorageRoles.legacyBucketReader(), newHashSet(projectViewer), + StorageRoles.legacyObjectReader(), newHashSet(Identity.allUsers())); + + // Validate getting policy. + Policy currentPolicy = storage.getPolicy(BUCKET); + assertEquals(bindingsWithoutPublicRead, currentPolicy.getBindings()); + + // Validate updating policy. + Policy updatedPolicy = + storage.updatePolicy( + BUCKET, + currentPolicy.toBuilder() + .addIdentity(StorageRoles.legacyObjectReader(), Identity.allUsers()) + .build()); + assertEquals(bindingsWithPublicRead, updatedPolicy.getBindings()); + Policy revertedPolicy = + storage.updatePolicy( + BUCKET, + updatedPolicy.toBuilder() + .removeIdentity(StorageRoles.legacyObjectReader(), Identity.allUsers()) + .build()); + assertEquals(bindingsWithoutPublicRead, revertedPolicy.getBindings()); + + // Validate testing permissions. + List expectedPermissions = ImmutableList.of(true, true); + assertEquals( + expectedPermissions, + storage.testPermissions( + BUCKET, + ImmutableList.of("storage.buckets.getIamPolicy", "storage.buckets.setIamPolicy"))); + } } diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java new file mode 100644 index 000000000000..501b61b4294e --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java @@ -0,0 +1,104 @@ +/* + * 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.testing; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.easymock.EasyMock; +import org.easymock.IArgumentMatcher; + +import com.google.api.services.storage.model.Policy; +import com.google.api.services.storage.model.Policy.Bindings; +import com.google.common.collect.ImmutableSet; + +/** + * Matches two {@link Policy} instances, which may have lists of {@link Bindings} that are not in + * the same order but which are still logically equivalent. + */ +public class ApiPolicyMatcher implements IArgumentMatcher { + + private final Map> expectedBindings; + private final String expectedEtag; + + public ApiPolicyMatcher(Policy expected) { + expectedBindings = toMap(expected.getBindings()); + expectedEtag = expected.getEtag(); + } + + @Override + public boolean matches(Object object) { + if (!(object instanceof Policy)) { + return false; + } + Policy actual = (Policy) object; + Map> actualBindings = toMap(actual.getBindings()); + String actualEtag = actual.getEtag(); + + if (expectedEtag == null) { + if (actualEtag != null) { + return false; + } + } else { + if (!expectedEtag.equals(actual.getEtag())) { + return false; + } + } + + if (expectedBindings.size() != actualBindings.size()) { + return false; + } + + for (Map.Entry> entry : expectedBindings.entrySet()) { + String role = entry.getKey(); + Set expectedMembers = entry.getValue(); + Set actualMembers = actualBindings.get(role); + if (!expectedMembers.equals(actualMembers)) { + return false; + } + } + + return true; + } + + @Override + public void appendTo(StringBuffer buffer) { + buffer.append("eqApiPolicy("); + buffer.append("etag="); + buffer.append(expectedEtag); + buffer.append(",bindings="); + buffer.append(expectedBindings); + buffer.append(")"); + } + + public static Policy eqApiPolicy(Policy in) { + EasyMock.reportMatcher(new ApiPolicyMatcher(in)); + return null; + } + + private Map> toMap(List bindings) { + Map> mapBindings = new HashMap<>(); + if (bindings != null) { + for (Bindings binding : bindings) { + mapBindings.put(binding.getRole(), ImmutableSet.copyOf(binding.getMembers())); + } + } + return mapBindings; + } +} diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java new file mode 100644 index 000000000000..b1b6856ed1f4 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java @@ -0,0 +1,115 @@ +/* + * 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.testing; + +import static com.google.cloud.storage.testing.ApiPolicyMatcher.eqApiPolicy; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.easymock.EasyMock; +import org.junit.Test; + +import com.google.api.services.storage.model.Policy; +import com.google.api.services.storage.model.Policy.Bindings; +import com.google.common.collect.ImmutableList; + +public class ApiPolicyMatcherTest { + + private static interface PolicyAcceptor { + int accept(Policy policy); + } + + private static final String ETAG = "CAE="; + private static final Policy API_POLICY_1 = + new Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test1@gmail.com", + "user:test2@gmail.com")) + .setRole("roles/storage.objectAdmin"), + new Bindings() + .setMembers(ImmutableList.of("group:test-group@gmail.com")) + .setRole("roles/storage.admin"))) + .setEtag(ETAG); + private static final Policy API_POLICY_2 = + new Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("group:test-group@gmail.com")) + .setRole("roles/storage.admin"), + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test2@gmail.com", + "user:test1@gmail.com")) + .setRole("roles/storage.objectAdmin"))) + .setEtag(ETAG); + private static final Policy API_POLICY_MISSING_BINDINGS = + new Policy().setEtag(ETAG); + private static final Policy API_POLICY_MISSING_ETAG = + new Policy() + .setBindings(ImmutableList.of( + new Bindings() + .setMembers(ImmutableList.of("group:test-group@gmail.com")) + .setRole("roles/storage.admin"), + new Bindings() + .setMembers(ImmutableList.of("allUsers")) + .setRole("roles/storage.objectViewer"), + new Bindings() + .setMembers( + ImmutableList.of( + "user:test2@gmail.com", + "user:test1@gmail.com")) + .setRole("roles/storage.objectAdmin"))); + + @Test + public void testEquivalence() { + assertMatch(API_POLICY_1, API_POLICY_2); + assertMatch(API_POLICY_2, API_POLICY_1); + assertNoMatch(API_POLICY_1, API_POLICY_MISSING_BINDINGS); + assertNoMatch(API_POLICY_MISSING_BINDINGS, API_POLICY_1); + assertNoMatch(API_POLICY_1, API_POLICY_MISSING_ETAG); + assertNoMatch(API_POLICY_MISSING_ETAG, API_POLICY_1); + assertNoMatch(API_POLICY_MISSING_BINDINGS, API_POLICY_MISSING_ETAG); + assertNoMatch(API_POLICY_MISSING_ETAG, API_POLICY_MISSING_BINDINGS); + } + + private static void assertMatch(Policy expected, Policy actual) { + assertTrue(new ApiPolicyMatcher(expected).matches(actual)); + } + + private static void assertNoMatch(Policy expected, Policy actual) { + assertFalse(new ApiPolicyMatcher(expected).matches(actual)); + } + + @Test + public void testStaticMocker() { + PolicyAcceptor mock = EasyMock.createMock(PolicyAcceptor.class); + EasyMock.expect(mock.accept(eqApiPolicy(API_POLICY_1))).andReturn(0); + EasyMock.replay(mock); + mock.accept(API_POLICY_2); + EasyMock.verify(mock); + } +}