diff --git a/google-cloud-core/pom.xml b/google-cloud-core/pom.xml index 342b3ce47e..6789692116 100644 --- a/google-cloud-core/pom.xml +++ b/google-cloud-core/pom.xml @@ -27,6 +27,10 @@ com.google.api gax + + com.google.auto.value + auto-value-annotations + com.google.protobuf protobuf-java-util @@ -83,6 +87,10 @@ objenesis test + + com.google.code.findbugs + jsr305 + com.google.truth truth diff --git a/google-cloud-core/src/main/java/com/google/cloud/Binding.java b/google-cloud-core/src/main/java/com/google/cloud/Binding.java new file mode 100644 index 0000000000..be16f87421 --- /dev/null +++ b/google-cloud-core/src/main/java/com/google/cloud/Binding.java @@ -0,0 +1,112 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import static com.google.common.base.Predicates.in; +import static com.google.common.base.Predicates.not; + +import com.google.api.core.BetaApi; +import com.google.auto.value.AutoValue; +import com.google.common.base.Predicate; +import com.google.common.collect.Collections2; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nullable; + +/** + * Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access + * settings for Cloud Platform resources. A policy is a list of bindings. A binding assigns a set of + * identities to a role, where the identities can be user accounts, Google groups, Google domains, + * and service accounts. A role is a named list of permissions defined by IAM. + * + * @see Policy + */ +@BetaApi("This is a Beta API is not stable yet and may change in the future.") +@AutoValue +public abstract class Binding { + /** Get IAM Policy Binding Role */ + public abstract String getRole(); + + /** Get IAM Policy Binding Members */ + public abstract ImmutableList getMembers(); + + /** Get IAM Policy Binding Condition */ + @Nullable + public abstract Condition getCondition(); + + /** Create a Binding.Builder from an existing Binding */ + public abstract Builder toBuilder(); + + /** Create a new Binding.Builder */ + public static Builder newBuilder() { + List emptyMembers = ImmutableList.of(); + return new AutoValue_Binding.Builder().setMembers(emptyMembers); + } + + @AutoValue.Builder + public abstract static class Builder { + /** + * Set IAM Role for Policy Binding + * + * @throws NullPointerException if the role is null. + */ + public abstract Builder setRole(String role); + + /** + * Set IAM Members for Policy Binding + * + * @throws NullPointerException if a member is null. + */ + public abstract Builder setMembers(Iterable members); + + /** Set IAM Condition for Policy Binding */ + public abstract Builder setCondition(Condition condition); + + /** Internal use to getMembers() in addMembers() and removeMembers() */ + abstract ImmutableList getMembers(); + + /** + * Add members to Policy Binding. + * + * @throws NullPointerException if a member is null. + */ + public Builder addMembers(String member, String... moreMembers) { + ImmutableList.Builder membersBuilder = ImmutableList.builder(); + membersBuilder.addAll(getMembers()); + membersBuilder.addAll(Lists.asList(member, moreMembers)); + setMembers(membersBuilder.build()); + return this; + } + + /** + * Remove members to Policy Binding. + * + * @throws NullPointerException if a member is null. + */ + public Builder removeMembers(String... members) { + Predicate selectMembersNotInList = not(in(Arrays.asList(members))); + Collection filter = Collections2.filter(getMembers(), selectMembersNotInList); + setMembers(filter); + return this; + } + + public abstract Binding build(); + } +} diff --git a/google-cloud-core/src/main/java/com/google/cloud/Condition.java b/google-cloud-core/src/main/java/com/google/cloud/Condition.java new file mode 100644 index 0000000000..3854883796 --- /dev/null +++ b/google-cloud-core/src/main/java/com/google/cloud/Condition.java @@ -0,0 +1,65 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import com.google.api.core.BetaApi; +import com.google.auto.value.AutoValue; + +/** + * Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access + * settings for Cloud Platform resources. A policy is a list of bindings. A binding assigns a set of + * identities to a role, where the identities can be user accounts, Google groups, Google domains, + * and service accounts. A role is a named list of permissions defined by IAM. + * + * @see Policy + * @see IAM Conditions + */ +@BetaApi("This is a Beta API is not stable yet and may change in the future.") +@AutoValue +public abstract class Condition { + /** Get IAM Policy Binding Condition Title */ + public abstract String getTitle(); + + /** Get IAM Policy Binding Condition Description */ + public abstract String getDescription(); + + /** Get IAM Policy Binding Condition Expression */ + public abstract String getExpression(); + + /** Create a new Condition.Builder from an existing Condition */ + public abstract Builder toBuilder(); + + /** Create a new Condition.Builder */ + public static Builder newBuilder() { + return new AutoValue_Condition.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + /** Set IAM Policy Binding Condition Title */ + public abstract Builder setTitle(String title); + + /** Set IAM Policy Binding Condition Description */ + public abstract Builder setDescription(String description); + + /** Set IAM Policy Binding Condition Expression */ + public abstract Builder setExpression(String expression); + + /** Build Builder which creates a Condition instance */ + public abstract Condition build(); + } +} 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 84067ec87e..a6ecbae11e 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 @@ -21,19 +21,16 @@ import com.google.api.core.ApiFunction; import com.google.api.core.InternalApi; -import com.google.common.base.Function; import com.google.common.base.MoreObjects; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Lists; import com.google.common.io.BaseEncoding; import com.google.protobuf.ByteString; +import com.google.type.Expr; import java.io.Serializable; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -42,20 +39,35 @@ /** * Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access - * settings for Cloud Platform resources. A policy is a map of bindings. A binding assigns a set of + * settings for Cloud Platform resources. A policy is a list of bindings. A binding assigns a set of * identities to a role, where the identities can be user accounts, Google groups, Google domains, * and service accounts. A role is a named list of permissions defined by IAM. * - * @see Policy + * @see Policy */ public final class Policy implements Serializable { private static final long serialVersionUID = -3348914530232544290L; - - private final Map> bindings; + private final ImmutableList bindingsList; private final String etag; private final int version; + /* + * Return true if Policy is version 3 OR bindings has a conditional binding. + * Return false if Policy is version 1 AND bindings does not have a conditional binding. + */ + private static boolean isConditional(int version, List bindingsList) { + for (Binding binding : bindingsList) { + if (binding.getCondition() != null) { + return true; + } + } + if (version == 3) { + return true; + } + return false; + } + public abstract static class Marshaller { @InternalApi("This class should only be extended within google-cloud-java") @@ -85,22 +97,25 @@ public static class DefaultMarshaller extends Marshaller> bindings = new HashMap<>(); + ImmutableList.Builder bindingsListBuilder = ImmutableList.builder(); for (com.google.iam.v1.Binding bindingPb : policyPb.getBindingsList()) { - bindings.put( - Role.of(bindingPb.getRole()), - ImmutableSet.copyOf( - Lists.transform( - bindingPb.getMembersList(), - new Function() { - @Override - public Identity apply(String s) { - return IDENTITY_VALUE_OF_FUNCTION.apply(s); - } - }))); + Binding.Builder convertedBinding = + Binding.newBuilder() + .setRole(bindingPb.getRole()) + .setMembers(bindingPb.getMembersList()); + if (bindingPb.hasCondition()) { + Expr expr = bindingPb.getCondition(); + convertedBinding.setCondition( + Condition.newBuilder() + .setTitle(expr.getTitle()) + .setDescription(expr.getDescription()) + .setExpression(expr.getExpression()) + .build()); + } + bindingsListBuilder.add(convertedBinding.build()); } return newBuilder() - .setBindings(bindings) + .setBindings(bindingsListBuilder.build()) .setEtag( policyPb.getEtag().isEmpty() ? null @@ -113,18 +128,19 @@ public Identity apply(String s) { protected com.google.iam.v1.Policy toPb(Policy policy) { com.google.iam.v1.Policy.Builder policyBuilder = com.google.iam.v1.Policy.newBuilder(); List bindingPbList = new LinkedList<>(); - for (Map.Entry> binding : policy.getBindings().entrySet()) { + for (Binding binding : policy.getBindingsList()) { com.google.iam.v1.Binding.Builder bindingBuilder = com.google.iam.v1.Binding.newBuilder(); - bindingBuilder.setRole(binding.getKey().getValue()); - bindingBuilder.addAllMembers( - Lists.transform( - new ArrayList<>(binding.getValue()), - new Function() { - @Override - public String apply(Identity identity) { - return IDENTITY_STR_VALUE_FUNCTION.apply(identity); - } - })); + bindingBuilder.setRole(binding.getRole()); + bindingBuilder.addAllMembers(binding.getMembers()); + if (binding.getCondition() != null) { + Condition condition = binding.getCondition(); + bindingBuilder.setCondition( + Expr.newBuilder() + .setTitle(condition.getTitle()) + .setDescription(condition.getDescription()) + .setExpression(condition.getExpression()) + .build()); + } bindingPbList.add(bindingBuilder.build()); } policyBuilder.addAllBindings(bindingPbList); @@ -138,8 +154,7 @@ public String apply(Identity identity) { /** A builder for {@code Policy} objects. */ public static class Builder { - - private final Map> bindings = new HashMap<>(); + private final List bindingsList = new ArrayList(); private String etag; private int version; @@ -148,7 +163,7 @@ protected Builder() {} @InternalApi("This class should only be extended within google-cloud-java") protected Builder(Policy policy) { - setBindings(policy.bindings); + bindingsList.addAll(policy.bindingsList); setEtag(policy.etag); setVersion(policy.version); } @@ -157,26 +172,73 @@ protected Builder(Policy policy) { * Replaces the builder's map of bindings with the given map of bindings. * * @throws NullPointerException if the given map is null or contains any null keys or values - * @throws IllegalArgumentException if any identities in the given map are null + * @throws IllegalArgumentException if any identities in the given map are null or if policy + * version is equal to 3 or has conditional bindings because conditional policies are not + * supported */ public final Builder setBindings(Map> bindings) { checkNotNull(bindings, "The provided map of bindings cannot be null."); + checkArgument( + !isConditional(this.version, this.bindingsList), + "setBindings() is only supported with version 1 policies and non-conditional policies"); for (Map.Entry> binding : bindings.entrySet()) { checkNotNull(binding.getKey(), "The role cannot be null."); Set identities = binding.getValue(); checkNotNull(identities, "A role cannot be assigned to a null set of identities."); checkArgument(!identities.contains(null), "Null identities are not permitted."); } - this.bindings.clear(); + // convert into List format + this.bindingsList.clear(); for (Map.Entry> binding : bindings.entrySet()) { - this.bindings.put(binding.getKey(), new HashSet<>(binding.getValue())); + Binding.Builder bindingBuilder = Binding.newBuilder(); + bindingBuilder.setRole(binding.getKey().getValue()); + ImmutableList.Builder membersBuilder = ImmutableList.builder(); + for (Identity identity : binding.getValue()) { + membersBuilder.add(identity.strValue()); + } + bindingBuilder.setMembers(membersBuilder.build()); + this.bindingsList.add(bindingBuilder.build()); } return this; } - /** Removes the role (and all identities associated with that role) from the policy. */ + /** + * Replaces the builder's List of bindings with the given List of Bindings. + * + * @throws NullPointerException if the given list is null, role is null, or contains any null + * members in bindings + */ + public final Builder setBindings(List bindings) { + this.bindingsList.clear(); + for (Binding binding : bindings) { + Binding.Builder bindingBuilder = Binding.newBuilder(); + bindingBuilder.setMembers(ImmutableList.copyOf(binding.getMembers())); + bindingBuilder.setRole(binding.getRole()); + bindingBuilder.setCondition(binding.getCondition()); + this.bindingsList.add(bindingBuilder.build()); + } + return this; + } + + /** + * Removes the role (and all identities associated with that role) from the policy. + * + * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings + * because conditional policies are not supported + */ public final Builder removeRole(Role role) { - bindings.remove(role); + checkArgument( + !isConditional(this.version, this.bindingsList), + "removeRole() is only supported with version 1 policies and non-conditional policies"); + Iterator iterator = bindingsList.iterator(); + + while (iterator.hasNext()) { + Binding binding = (Binding) iterator.next(); + if (binding.getRole().equals(role.getValue())) { + iterator.remove(); + return this; + } + } return this; } @@ -184,39 +246,79 @@ public final Builder removeRole(Role role) { * Adds one or more identities to the policy under the role specified. * * @throws NullPointerException if the role or any of the identities is null. + * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings. */ public final Builder addIdentity(Role role, Identity first, Identity... others) { + checkArgument( + !isConditional(this.version, this.bindingsList), + "addIdentity() is only supported with version 1 policies and non-conditional policies"); String nullIdentityMessage = "Null identities are not permitted."; checkNotNull(first, nullIdentityMessage); checkNotNull(others, nullIdentityMessage); - for (Identity identity : others) { - checkNotNull(identity, nullIdentityMessage); + checkNotNull(role, "The role cannot be null."); + for (int i = 0; i < bindingsList.size(); i++) { + Binding binding = bindingsList.get(i); + if (binding.getRole().equals(role.getValue())) { + Binding.Builder bindingBuilder = binding.toBuilder(); + ImmutableList.Builder membersBuilder = ImmutableList.builder(); + membersBuilder.addAll(binding.getMembers()); + membersBuilder.add(first.strValue()); + for (Identity identity : others) { + membersBuilder.add(identity.strValue()); + } + bindingBuilder.setMembers(membersBuilder.build()); + bindingsList.set(i, bindingBuilder.build()); + return this; + } } - Set toAdd = new LinkedHashSet<>(); - toAdd.add(first); - toAdd.addAll(Arrays.asList(others)); - Set identities = bindings.get(checkNotNull(role, "The role cannot be null.")); - if (identities == null) { - identities = new HashSet<>(); - bindings.put(role, identities); + // Binding does not yet exist. + Binding.Builder bindingBuilder = Binding.newBuilder().setRole(role.getValue()); + ImmutableList.Builder membersBuilder = ImmutableList.builder(); + membersBuilder.add(first.strValue()); + for (Identity identity : others) { + membersBuilder.add(identity.strValue()); } - identities.addAll(toAdd); + bindingBuilder.setMembers(membersBuilder.build()); + bindingsList.add(bindingBuilder.build()); return this; } /** * Removes one or more identities from an existing binding. Does nothing if the binding * associated with the provided role doesn't exist. + * + * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings */ public final Builder removeIdentity(Role role, Identity first, Identity... others) { - Set identities = bindings.get(role); - if (identities != null) { - identities.remove(first); - identities.removeAll(Arrays.asList(others)); + checkArgument( + !isConditional(this.version, this.bindingsList), + "removeIdentity() is only supported with version 1 policies and non-conditional policies"); + String nullIdentityMessage = "Null identities are not permitted."; + checkNotNull(first, nullIdentityMessage); + checkNotNull(others, nullIdentityMessage); + checkNotNull(role, "The role cannot be null."); + for (int i = 0; i < bindingsList.size(); i++) { + Binding binding = bindingsList.get(i); + if (binding.getRole().equals(role.getValue())) { + Binding.Builder bindingBuilder = binding.toBuilder().removeMembers(first.strValue()); + for (Identity identity : others) { + bindingBuilder.removeMembers(identity.strValue()); + } + Binding updatedBindings = bindingBuilder.build(); + bindingsList.set(i, updatedBindings); + break; + } } - if (identities != null && identities.isEmpty()) { - bindings.remove(role); + + Iterator iterator = bindingsList.iterator(); + while (iterator.hasNext()) { + Binding binding = (Binding) iterator.next(); + if (binding.getRole().equals(role.getValue()) && binding.getMembers().size() == 0) { + iterator.remove(); + break; + } } + return this; } @@ -236,11 +338,8 @@ public final Builder setEtag(String etag) { return this; } - /** - * Sets the version of the policy. The default version is 0, meaning only the "owner", "editor", - * and "viewer" roles are permitted. If the version is 1, you may also use other roles. - */ - protected final Builder setVersion(int version) { + /** Sets the version of the policy. */ + public final Builder setVersion(int version) { this.version = version; return this; } @@ -252,11 +351,7 @@ public final Policy build() { } private Policy(Builder builder) { - ImmutableMap.Builder> bindingsBuilder = ImmutableMap.builder(); - for (Map.Entry> binding : builder.bindings.entrySet()) { - bindingsBuilder.put(binding.getKey(), ImmutableSet.copyOf(binding.getValue())); - } - this.bindings = bindingsBuilder.build(); + this.bindingsList = ImmutableList.copyOf(builder.bindingsList); this.etag = builder.etag; this.version = builder.version; } @@ -266,9 +361,29 @@ public Builder toBuilder() { return new Builder(this); } - /** Returns the map of bindings that comprises the policy. */ + /** + * Returns the map of bindings that comprises the policy. + * + * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings + */ public Map> getBindings() { - return bindings; + checkArgument( + !isConditional(this.version, this.bindingsList), + "getBindings() is only supported with version 1 policies and non-conditional policies"); + ImmutableMap.Builder> bindingsV1Builder = ImmutableMap.builder(); + for (Binding binding : bindingsList) { + ImmutableSet.Builder identities = ImmutableSet.builder(); + for (String member : binding.getMembers()) { + identities.add(Identity.valueOf(member)); + } + bindingsV1Builder.put(Role.of(binding.getRole()), identities.build()); + } + return bindingsV1Builder.build(); + } + + /** Returns the list of bindings that comprises the policy for version 3. */ + public ImmutableList getBindingsList() { + return bindingsList; } /** @@ -297,7 +412,7 @@ public int getVersion() { @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("bindings", bindings) + .add("bindings", bindingsList) .add("etag", etag) .add("version", version) .toString(); @@ -305,7 +420,7 @@ public String toString() { @Override public int hashCode() { - return Objects.hash(getClass(), bindings, etag, version); + return Objects.hash(getClass(), bindingsList, etag, version); } @Override @@ -317,9 +432,10 @@ public boolean equals(Object obj) { return false; } Policy other = (Policy) obj; - return Objects.equals(bindings, other.getBindings()) - && Objects.equals(etag, other.getEtag()) - && Objects.equals(version, other.getVersion()); + if (!bindingsList.equals(other.getBindingsList())) { + return false; + } + return Objects.equals(etag, other.getEtag()) && version == other.getVersion(); } /** Returns a builder for {@code Policy} objects. */ diff --git a/google-cloud-core/src/test/java/com/google/cloud/PolicyTest.java b/google-cloud-core/src/test/java/com/google/cloud/PolicyTest.java index 61d99223e4..eb03e33726 100644 --- a/google-cloud-core/src/test/java/com/google/cloud/PolicyTest.java +++ b/google-cloud-core/src/test/java/com/google/cloud/PolicyTest.java @@ -125,7 +125,7 @@ public void testIllegalPolicies() { assertEquals("Null identities are not permitted.", ex.getMessage()); } try { - Policy.newBuilder().setBindings(null); + Policy.newBuilder().setBindings((Map>) null); fail("Null bindings map should cause exception."); } catch (NullPointerException ex) { assertEquals("The provided map of bindings cannot be null.", ex.getMessage()); diff --git a/google-cloud-core/src/test/java/com/google/cloud/PolicyV3Test.java b/google-cloud-core/src/test/java/com/google/cloud/PolicyV3Test.java new file mode 100644 index 0000000000..13cab4332c --- /dev/null +++ b/google-cloud-core/src/test/java/com/google/cloud/PolicyV3Test.java @@ -0,0 +1,284 @@ +/* + * Copyright 2020 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.cloud; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import com.google.cloud.Policy.DefaultMarshaller; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; +import org.junit.Test; + +public class PolicyV3Test { + + private static final String ALL_USERS = "allUsers"; + private static final String ALL_AUTH_USERS = "allAuthenticatedUsers"; + private static final String USER = "user:abc@gmail.com"; + private static final String SERVICE_ACCOUNT = "serviceAccount:service-account@gmail.com"; + private static final String GROUP = "group:group@gmail.com"; + private static final String DOMAIN = "domain:google.com"; + private static final String VIEWER = "roles/viewer"; + private static final String EDITOR = "roles/editor"; + private static final String OWNER = "roles/owner"; + private static final List MEMBERS_LIST_1 = + ImmutableList.of(USER, SERVICE_ACCOUNT, ALL_USERS); + private static final List MEMBERS_LIST_2 = + ImmutableList.of(ALL_AUTH_USERS, GROUP, DOMAIN); + private static final List BINDINGS_NO_CONDITIONS = + ImmutableList.of( + Binding.newBuilder().setRole(VIEWER).setMembers(MEMBERS_LIST_1).build(), + Binding.newBuilder().setRole(EDITOR).setMembers(MEMBERS_LIST_2).build()); + private static final List BINDINGS_WITH_CONDITIONS = + ImmutableList.copyOf(BINDINGS_NO_CONDITIONS) + .of( + Binding.newBuilder() + .setRole(VIEWER) + .setMembers(MEMBERS_LIST_1) + .setCondition( + Condition.newBuilder() + .setTitle("Condition") + .setDescription("Condition") + .setExpression("Expr") + .build()) + .build(), + Binding.newBuilder().setRole(EDITOR).setMembers(MEMBERS_LIST_2).build()); + private static final Policy FULL_POLICY_V1 = + Policy.newBuilder().setBindings(BINDINGS_NO_CONDITIONS).setEtag("etag").setVersion(1).build(); + + private static final Policy FULL_POLICY_V3 = + Policy.newBuilder() + .setBindings(BINDINGS_WITH_CONDITIONS) + .setEtag("etag") + .setVersion(3) + .build(); + + private static final Policy FULL_POLICY_V3_WITH_VERSION_1 = + Policy.newBuilder() + .setBindings(BINDINGS_WITH_CONDITIONS) + .setEtag("etag") + .setVersion(1) + .build(); + + @Test + public void testBuilderV1() { + assertEquals(BINDINGS_NO_CONDITIONS, FULL_POLICY_V1.getBindingsList()); + assertEquals(1, FULL_POLICY_V1.getVersion()); + assertEquals("etag", FULL_POLICY_V1.getEtag()); + Policy policy = FULL_POLICY_V1.toBuilder().setBindings(BINDINGS_NO_CONDITIONS).build(); + assertEquals(BINDINGS_NO_CONDITIONS, policy.getBindingsList()); + assertEquals("etag", policy.getEtag()); + assertEquals(1, policy.getVersion()); + } + + @Test + public void testBuilderV3WithConditions() { + assertEquals(BINDINGS_WITH_CONDITIONS, FULL_POLICY_V3.getBindingsList()); + assertEquals(3, FULL_POLICY_V3.getVersion()); + assertEquals("etag", FULL_POLICY_V3.getEtag()); + Policy policy = FULL_POLICY_V3.toBuilder().setBindings(BINDINGS_WITH_CONDITIONS).build(); + assertEquals(BINDINGS_WITH_CONDITIONS, policy.getBindingsList()); + assertEquals("etag", policy.getEtag()); + assertEquals(3, policy.getVersion()); + } + + @Test + public void testBuilderV1ToV3Compatability() { + assertEquals(BINDINGS_WITH_CONDITIONS, FULL_POLICY_V3_WITH_VERSION_1.getBindingsList()); + assertEquals(1, FULL_POLICY_V3_WITH_VERSION_1.getVersion()); + assertEquals("etag", FULL_POLICY_V3_WITH_VERSION_1.getEtag()); + Policy policy = + FULL_POLICY_V3_WITH_VERSION_1 + .toBuilder() + .setBindings(BINDINGS_WITH_CONDITIONS) + .setVersion(3) + .build(); + assertEquals(BINDINGS_WITH_CONDITIONS, policy.getBindingsList()); + assertEquals("etag", policy.getEtag()); + assertEquals(3, policy.getVersion()); + } + + @Test + public void removeMemberFromPolicy() { + assertEquals(3, FULL_POLICY_V3.getBindingsList().get(0).getMembers().size()); + List bindings = new ArrayList<>(FULL_POLICY_V3.getBindingsList()); + + for (int i = 0; i < bindings.size(); i++) { + Binding binding = bindings.get(i); + if (binding.getRole().equals(VIEWER)) { + bindings.set(i, binding.toBuilder().removeMembers(ALL_USERS).build()); + break; + } + } + + Policy updatedPolicy = FULL_POLICY_V3.toBuilder().setBindings(bindings).build(); + assertEquals(2, updatedPolicy.getBindingsList().get(0).getMembers().size()); + } + + @Test + public void addMemberFromPolicy() { + assertEquals(3, FULL_POLICY_V3.getBindingsList().get(0).getMembers().size()); + List bindings = new ArrayList<>(FULL_POLICY_V3.getBindingsList()); + + for (int i = 0; i < bindings.size(); i++) { + Binding binding = bindings.get(i); + if (binding.getRole().equals(VIEWER)) { + bindings.set(i, binding.toBuilder().addMembers("user:example@example.com").build()); + } + } + + Policy updatedPolicy = FULL_POLICY_V3.toBuilder().setBindings(bindings).build(); + assertEquals(4, updatedPolicy.getBindingsList().get(0).getMembers().size()); + } + + @Test + public void removeBindingFromPolicy() { + assertEquals(2, FULL_POLICY_V3.getBindingsList().size()); + List bindings = new ArrayList<>(FULL_POLICY_V3.getBindingsList()); + + Iterator iterator = bindings.iterator(); + while (iterator.hasNext()) { + Binding binding = (Binding) iterator.next(); + if (binding.getRole().equals(EDITOR) && binding.getCondition() == null) { + iterator.remove(); + break; + } + } + + Policy updatedPolicy = FULL_POLICY_V3.toBuilder().setBindings(bindings).build(); + assertEquals(1, updatedPolicy.getBindingsList().size()); + } + + @Test + public void addBindingToPolicy() { + assertEquals(2, FULL_POLICY_V3.getBindingsList().size()); + List bindings = new ArrayList<>(FULL_POLICY_V3.getBindingsList()); + bindings.add(Binding.newBuilder().setRole(OWNER).setMembers(ImmutableList.of(USER)).build()); + Policy updatedPolicy = FULL_POLICY_V3.toBuilder().setBindings(bindings).build(); + assertEquals(3, updatedPolicy.getBindingsList().size()); + } + + @Test + public void testIllegalPolicies() { + try { + Binding.newBuilder().setRole(null).build(); + fail("Null role should cause exception."); + } catch (NullPointerException ex) { + assertEquals("Null role", ex.getMessage()); + } + try { + FULL_POLICY_V3 + .toBuilder() + .setBindings( + Arrays.asList( + Binding.newBuilder() + .setRole("test") + .setMembers(Arrays.asList(null, "user")) + .build())) + .build(); + fail("Null member should cause exception."); + } catch (NullPointerException ex) { + assertEquals("at index 0", ex.getMessage()); + } + try { + FULL_POLICY_V3.getBindings(); + fail("getBindings() should cause exception with Policy V3."); + } catch (IllegalArgumentException ex) { + assertEquals( + "getBindings() is only supported with version 1 policies and non-conditional policies", + ex.getMessage()); + } + try { + FULL_POLICY_V3.toBuilder().addIdentity(Role.editor(), Identity.allUsers()); + fail("getBindings() should cause exception with Policy V3."); + } catch (IllegalArgumentException ex) { + assertEquals( + "addIdentity() is only supported with version 1 policies and non-conditional policies", + ex.getMessage()); + } + try { + FULL_POLICY_V3.toBuilder().removeIdentity(Role.editor(), Identity.allUsers()); + fail("getBindings() should cause exception with Policy V3."); + } catch (IllegalArgumentException ex) { + assertEquals( + "removeIdentity() is only supported with version 1 policies and non-conditional policies", + ex.getMessage()); + } + try { + FULL_POLICY_V3.toBuilder().setBindings(FULL_POLICY_V1.getBindings()); + fail("getBindings() should cause exception with Policy V3."); + } catch (IllegalArgumentException ex) { + assertEquals( + "setBindings() is only supported with version 1 policies and non-conditional policies", + ex.getMessage()); + } + } + + @Test + public void testEqualsHashCode() { + assertNotNull(FULL_POLICY_V3); + Policy emptyPolicy = Policy.newBuilder().build(); + Policy anotherPolicy = Policy.newBuilder().build(); + assertEquals(emptyPolicy, anotherPolicy); + assertEquals(emptyPolicy.hashCode(), anotherPolicy.hashCode()); + assertNotEquals(FULL_POLICY_V3, FULL_POLICY_V1); + assertNotEquals(FULL_POLICY_V3.hashCode(), FULL_POLICY_V1.hashCode()); + Policy copy = FULL_POLICY_V1.toBuilder().build(); + assertEquals(FULL_POLICY_V1, copy); + assertEquals(FULL_POLICY_V1.hashCode(), copy.hashCode()); + } + + @Test + public void testBindings() { + assertTrue(Policy.newBuilder().build().getBindingsList().isEmpty()); + assertEquals(BINDINGS_WITH_CONDITIONS, FULL_POLICY_V3.getBindingsList()); + } + + @Test + public void testEtag() { + assertNotNull(FULL_POLICY_V3.getEtag()); + assertEquals("etag", FULL_POLICY_V3.getEtag()); + } + + @Test + public void testVersion() { + assertEquals(1, FULL_POLICY_V1.getVersion()); + assertEquals(3, FULL_POLICY_V3.getVersion()); + assertEquals(1, FULL_POLICY_V3_WITH_VERSION_1.getVersion()); + } + + @Test + public void testDefaultMarshaller() { + DefaultMarshaller marshaller = new DefaultMarshaller(); + Policy emptyPolicy = Policy.newBuilder().build(); + assertEquals(emptyPolicy, marshaller.fromPb(marshaller.toPb(emptyPolicy))); + assertEquals(FULL_POLICY_V3, marshaller.fromPb(marshaller.toPb(FULL_POLICY_V3))); + assertEquals(FULL_POLICY_V1, marshaller.fromPb(marshaller.toPb(FULL_POLICY_V1))); + com.google.iam.v1.Policy policyPb = com.google.iam.v1.Policy.getDefaultInstance(); + Policy policy = marshaller.fromPb(policyPb); + assertTrue(policy.getBindingsList().isEmpty()); + assertNull(policy.getEtag()); + assertEquals(0, policy.getVersion()); + } +} diff --git a/pom.xml b/pom.xml index edec8b77ec..c3ee892baa 100644 --- a/pom.xml +++ b/pom.xml @@ -151,7 +151,8 @@ UTF-8 github google-cloud-core-parent - + 1.7 + 1.7 1.53.1 1.8.1 1.17.0 @@ -231,7 +232,12 @@ pom import - + + com.google.auto.value + auto-value-annotations + ${auto-value-annotations.version} + true + com.google.api api-common @@ -247,7 +253,6 @@ proto-google-iam-v1 ${google.iam.version} - io.opencensus @@ -324,7 +329,20 @@ org.objenesis:objenesis + + maven-compiler-plugin + + + + com.google.auto.value + auto-value + ${auto-value.version} + + + + +