From 127bb6ffaf76eb66dff9efa30186c65a850c221f Mon Sep 17 00:00:00 2001 From: rybosome Date: Thu, 23 Mar 2017 11:54:33 -0700 Subject: [PATCH 1/4] Add Identity Access Management (IAM) to the Storage API Adds support for bucket-level IAM (currently in limited alpha). More information about IAM in Google Cloud Storage can be found at https://cloud.google.com/storage/docs/access-control/iam --- .../storage/contrib/nio/FakeStorageRpc.java | 18 ++ .../main/java/com/google/cloud/Policy.java | 3 +- .../google/cloud/storage/PolicyHelper.java | 62 ++++++ .../com/google/cloud/storage/Storage.java | 82 +++++-- .../com/google/cloud/storage/StorageImpl.java | 86 ++++++-- .../cloud/storage/spi/v1/HttpStorageRpc.java | 29 +++ .../cloud/storage/spi/v1/StorageRpc.java | 23 ++ .../storage/testing/ApiPolicyComparator.java | 76 +++++++ .../cloud/storage/PolicyHelperTest.java | 65 ++++++ .../google/cloud/storage/StorageImplTest.java | 201 +++++++++++++++--- .../cloud/storage/it/ITStorageTest.java | 112 +++++++--- .../testing/ApiPolicyComparatorTest.java | 69 ++++++ 12 files changed, 732 insertions(+), 94 deletions(-) create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/PolicyHelper.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java 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..c9157d3bb77e 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. * @@ -1298,7 +1299,7 @@ public static Builder newBuilder() { return new Builder(); } } - + /** * Creates a new bucket. * @@ -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/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java new file mode 100644 index 000000000000..56c41ba9f3db --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java @@ -0,0 +1,76 @@ +/* + * 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.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.google.api.services.storage.model.Policy; +import com.google.api.services.storage.model.Policy.Bindings; +import com.google.common.collect.ImmutableSet; + +/** + * Compares 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 ApiPolicyComparator implements Comparator { + + public static final ApiPolicyComparator INSTANCE = new ApiPolicyComparator(); + + private ApiPolicyComparator() { + // Intentionally left blank. + } + + @Override + public int compare(Policy p1, Policy p2) { + int etagComparison = p1.getEtag().compareTo(p2.getEtag()); + if (etagComparison != 0) { return etagComparison; } + + Map> map1 = toMap(p1.getBindings()); + Map> map2 = toMap(p2.getBindings()); + + if (map1.size() != map2.size()) { + return map1.size() - map2.size(); + } + + for (Map.Entry> entry : map1.entrySet()) { + String role = entry.getKey(); + Set p1Members = entry.getValue(); + Set p2Members = map2.get(role); + if (p2Members == null) { + return -1; + } + if (!p1Members.equals(p2Members)) { + return p1Members.toString().compareTo(p2Members.toString()); + } + } + + return 0; + } + + private Map> toMap(List bindings) { + Map> mapBindings = new HashMap<>(); + 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/PolicyHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java new file mode 100644 index 000000000000..b0693e93e9bd --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java @@ -0,0 +1,65 @@ +/* + * 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 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.ApiPolicyComparator; +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); + assertEquals(0, ApiPolicyComparator.INSTANCE.compare(apiPolicy, 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..fc7a6b595b33 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,8 @@ package com.google.cloud.storage; +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 +27,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; @@ -47,45 +87,13 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.Tuple; import com.google.cloud.storage.spi.StorageRpcFactory; +import com.google.cloud.storage.testing.ApiPolicyComparator; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; 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), + cmp(preCommitApiPolicy, ApiPolicyComparator.INSTANCE, LogicalOperator.EQUAL))) + .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/ApiPolicyComparatorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java new file mode 100644 index 000000000000..55c7b1088090 --- /dev/null +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java @@ -0,0 +1,69 @@ +/* + * 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 org.junit.Assert.assertEquals; + +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 ApiPolicyComparatorTest { + + private static final String ETAG = "CAE="; + + @Test + public void testEquivalence() { + // Intentionally create 2 API policies with different binding and member ordering. + Policy apiPolicy1 = + 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); + Policy apiPolicy2 = + 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); + + assertEquals(0, ApiPolicyComparator.INSTANCE.compare(apiPolicy1, apiPolicy2)); + } +} From 6bd0ac04f1537d2184fdffde28f31a452493dba9 Mon Sep 17 00:00:00 2001 From: rybosome Date: Tue, 4 Apr 2017 16:09:38 -0700 Subject: [PATCH 2/4] Use a custom EasyMock matcher for API policies Rather than relying on a Comparator which doesn't fully implement the Comparable spec, instead implement a custom EasyMock matcher for verifying that API policies are equivalent in test code. --- google-cloud-storage/pom.xml | 5 +- .../storage/testing/ApiPolicyComparator.java | 76 ------------ .../storage/testing/ApiPolicyMatcher.java | 105 ++++++++++++++++ .../cloud/storage/PolicyHelperTest.java | 5 +- .../google/cloud/storage/StorageImplTest.java | 4 +- .../testing/ApiPolicyComparatorTest.java | 69 ----------- .../storage/testing/ApiPolicyMatcherTest.java | 115 ++++++++++++++++++ 7 files changed, 229 insertions(+), 150 deletions(-) delete mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java create mode 100644 google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java delete mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java create mode 100644 google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcherTest.java diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index f707f1d55397..ab0c9b6d2542 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -52,11 +52,14 @@ 4.12 test + org.easymock easymock 3.4 - test diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java deleted file mode 100644 index 56c41ba9f3db..000000000000 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyComparator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import com.google.api.services.storage.model.Policy; -import com.google.api.services.storage.model.Policy.Bindings; -import com.google.common.collect.ImmutableSet; - -/** - * Compares 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 ApiPolicyComparator implements Comparator { - - public static final ApiPolicyComparator INSTANCE = new ApiPolicyComparator(); - - private ApiPolicyComparator() { - // Intentionally left blank. - } - - @Override - public int compare(Policy p1, Policy p2) { - int etagComparison = p1.getEtag().compareTo(p2.getEtag()); - if (etagComparison != 0) { return etagComparison; } - - Map> map1 = toMap(p1.getBindings()); - Map> map2 = toMap(p2.getBindings()); - - if (map1.size() != map2.size()) { - return map1.size() - map2.size(); - } - - for (Map.Entry> entry : map1.entrySet()) { - String role = entry.getKey(); - Set p1Members = entry.getValue(); - Set p2Members = map2.get(role); - if (p2Members == null) { - return -1; - } - if (!p1Members.equals(p2Members)) { - return p1Members.toString().compareTo(p2Members.toString()); - } - } - - return 0; - } - - private Map> toMap(List bindings) { - Map> mapBindings = new HashMap<>(); - for (Bindings binding : bindings) { - mapBindings.put(binding.getRole(), ImmutableSet.copyOf(binding.getMembers())); - } - - return mapBindings; - } -} diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java new file mode 100644 index 000000000000..e9b0b5a64421 --- /dev/null +++ b/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java @@ -0,0 +1,105 @@ +/* + * 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/PolicyHelperTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/PolicyHelperTest.java index b0693e93e9bd..70b66f7b19d5 100644 --- 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 @@ -17,13 +17,14 @@ 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.ApiPolicyComparator; +import com.google.cloud.storage.testing.ApiPolicyMatcher; import com.google.common.collect.ImmutableList; public class PolicyHelperTest { @@ -60,6 +61,6 @@ public void testEquivalence() { PolicyHelper.convertToApiPolicy(libPolicy); assertEquals(libPolicy, actualLibPolicy); - assertEquals(0, ApiPolicyComparator.INSTANCE.compare(apiPolicy, actualApiPolicy)); + 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 fc7a6b595b33..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,7 @@ 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; @@ -87,7 +88,6 @@ import com.google.cloud.storage.spi.v1.StorageRpc; import com.google.cloud.storage.spi.v1.StorageRpc.Tuple; import com.google.cloud.storage.spi.StorageRpcFactory; -import com.google.cloud.storage.testing.ApiPolicyComparator; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; @@ -2040,7 +2040,7 @@ public void testUpdatePolicy() { EasyMock.expect( storageRpcMock.updatePolicy( eq(BUCKET_NAME1), - cmp(preCommitApiPolicy, ApiPolicyComparator.INSTANCE, LogicalOperator.EQUAL))) + eqApiPolicy(preCommitApiPolicy))) .andReturn(postCommitApiPolicy); EasyMock.replay(storageRpcMock); initializeService(); diff --git a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java deleted file mode 100644 index 55c7b1088090..000000000000 --- a/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyComparatorTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 org.junit.Assert.assertEquals; - -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 ApiPolicyComparatorTest { - - private static final String ETAG = "CAE="; - - @Test - public void testEquivalence() { - // Intentionally create 2 API policies with different binding and member ordering. - Policy apiPolicy1 = - 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); - Policy apiPolicy2 = - 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); - - assertEquals(0, ApiPolicyComparator.INSTANCE.compare(apiPolicy1, apiPolicy2)); - } -} 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); + } +} From 8eabb8543439628146be7be8817077b22c5d9c53 Mon Sep 17 00:00:00 2001 From: rybosome Date: Tue, 4 Apr 2017 16:13:28 -0700 Subject: [PATCH 3/4] Remove extraneous space --- .../src/main/java/com/google/cloud/storage/Storage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 c9157d3bb77e..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 @@ -1299,7 +1299,7 @@ public static Builder newBuilder() { return new Builder(); } } - + /** * Creates a new bucket. * From 9b5ce8cdd80076d0b3acdbf7bc9652a4df70b3a3 Mon Sep 17 00:00:00 2001 From: rybosome Date: Thu, 6 Apr 2017 16:36:06 -0700 Subject: [PATCH 4/4] Move ApiPolicyMatcher to the test package --- google-cloud-storage/pom.xml | 5 +---- .../com/google/cloud/storage/testing/ApiPolicyMatcher.java | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) rename google-cloud-storage/src/{main => test}/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java (99%) diff --git a/google-cloud-storage/pom.xml b/google-cloud-storage/pom.xml index ab0c9b6d2542..f707f1d55397 100644 --- a/google-cloud-storage/pom.xml +++ b/google-cloud-storage/pom.xml @@ -52,14 +52,11 @@ 4.12 test - org.easymock easymock 3.4 + test diff --git a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java similarity index 99% rename from google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java rename to google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java index e9b0b5a64421..501b61b4294e 100644 --- a/google-cloud-storage/src/main/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java +++ b/google-cloud-storage/src/test/java/com/google/cloud/storage/testing/ApiPolicyMatcher.java @@ -60,8 +60,7 @@ public boolean matches(Object object) { return false; } } - - + if (expectedBindings.size() != actualBindings.size()) { return false; }