• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2016 Google LLC
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *       http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.cloud;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 
22 import com.google.api.core.ApiFunction;
23 import com.google.api.core.InternalApi;
24 import com.google.common.base.MoreObjects;
25 import com.google.common.collect.ImmutableList;
26 import com.google.common.collect.ImmutableMap;
27 import com.google.common.collect.ImmutableSet;
28 import com.google.common.io.BaseEncoding;
29 import com.google.protobuf.ByteString;
30 import com.google.type.Expr;
31 import java.io.Serializable;
32 import java.util.ArrayList;
33 import java.util.Iterator;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.Objects;
38 import java.util.Set;
39 
40 /**
41  * Class for Identity and Access Management (IAM) policies. IAM policies are used to specify access
42  * settings for Cloud Platform resources. A policy is a list of bindings. A binding assigns a set of
43  * identities to a role, where the identities can be user accounts, Google groups, Google domains,
44  * and service accounts. A role is a named list of permissions defined by IAM.
45  *
46  * @see <a href="https://cloud.google.com/iam/docs/reference/rest/v1/Policy">Policy</a>
47  */
48 public final class Policy implements Serializable {
49 
50   private static final long serialVersionUID = -3348914530232544290L;
51   private final ImmutableList<Binding> bindingsList;
52   private final String etag;
53   private final int version;
54 
55   /*
56    * Return true if Policy is version 3 OR bindings has a conditional binding.
57    * Return false if Policy is version 1 AND bindings does not have a conditional binding.
58    */
isConditional(int version, List<Binding> bindingsList)59   private static boolean isConditional(int version, List<Binding> bindingsList) {
60     for (Binding binding : bindingsList) {
61       if (binding.getCondition() != null) {
62         return true;
63       }
64     }
65     if (version == 3) {
66       return true;
67     }
68     return false;
69   }
70 
71   public abstract static class Marshaller<T> {
72 
73     @InternalApi("This class should only be extended within google-cloud-java")
Marshaller()74     protected Marshaller() {}
75 
76     protected static final ApiFunction<String, Identity> IDENTITY_VALUE_OF_FUNCTION =
77         new ApiFunction<String, Identity>() {
78           @Override
79           public Identity apply(String identityPb) {
80             return Identity.valueOf(identityPb);
81           }
82         };
83     protected static final ApiFunction<Identity, String> IDENTITY_STR_VALUE_FUNCTION =
84         new ApiFunction<Identity, String>() {
85           @Override
86           public String apply(Identity identity) {
87             return identity.strValue();
88           }
89         };
90 
fromPb(T policyPb)91     protected abstract Policy fromPb(T policyPb);
92 
toPb(Policy policy)93     protected abstract T toPb(Policy policy);
94   }
95 
96   public static class DefaultMarshaller extends Marshaller<com.google.iam.v1.Policy> {
97 
98     @Override
fromPb(com.google.iam.v1.Policy policyPb)99     protected Policy fromPb(com.google.iam.v1.Policy policyPb) {
100       ImmutableList.Builder<Binding> bindingsListBuilder = ImmutableList.builder();
101       for (com.google.iam.v1.Binding bindingPb : policyPb.getBindingsList()) {
102         Binding.Builder convertedBinding =
103             Binding.newBuilder()
104                 .setRole(bindingPb.getRole())
105                 .setMembers(bindingPb.getMembersList());
106         if (bindingPb.hasCondition()) {
107           Expr expr = bindingPb.getCondition();
108           convertedBinding.setCondition(
109               Condition.newBuilder()
110                   .setTitle(expr.getTitle())
111                   .setDescription(expr.getDescription())
112                   .setExpression(expr.getExpression())
113                   .build());
114         }
115         bindingsListBuilder.add(convertedBinding.build());
116       }
117       return newBuilder()
118           .setBindings(bindingsListBuilder.build())
119           .setEtag(
120               policyPb.getEtag().isEmpty()
121                   ? null
122                   : BaseEncoding.base64().encode(policyPb.getEtag().toByteArray()))
123           .setVersion(policyPb.getVersion())
124           .build();
125     }
126 
127     @Override
toPb(Policy policy)128     protected com.google.iam.v1.Policy toPb(Policy policy) {
129       com.google.iam.v1.Policy.Builder policyBuilder = com.google.iam.v1.Policy.newBuilder();
130       List<com.google.iam.v1.Binding> bindingPbList = new LinkedList<>();
131       for (Binding binding : policy.getBindingsList()) {
132         com.google.iam.v1.Binding.Builder bindingBuilder = com.google.iam.v1.Binding.newBuilder();
133         bindingBuilder.setRole(binding.getRole());
134         bindingBuilder.addAllMembers(binding.getMembers());
135         if (binding.getCondition() != null) {
136           Condition condition = binding.getCondition();
137           bindingBuilder.setCondition(
138               Expr.newBuilder()
139                   .setTitle(condition.getTitle())
140                   .setDescription(condition.getDescription())
141                   .setExpression(condition.getExpression())
142                   .build());
143         }
144         bindingPbList.add(bindingBuilder.build());
145       }
146       policyBuilder.addAllBindings(bindingPbList);
147       if (policy.etag != null) {
148         policyBuilder.setEtag(ByteString.copyFrom(BaseEncoding.base64().decode(policy.etag)));
149       }
150       policyBuilder.setVersion(policy.version);
151       return policyBuilder.build();
152     }
153   }
154 
155   /** A builder for {@code Policy} objects. */
156   public static class Builder {
157     private final List<Binding> bindingsList = new ArrayList<Binding>();
158     private String etag;
159     private int version;
160 
161     @InternalApi("This class should only be extended within google-cloud-java")
Builder()162     protected Builder() {}
163 
164     @InternalApi("This class should only be extended within google-cloud-java")
Builder(Policy policy)165     protected Builder(Policy policy) {
166       bindingsList.addAll(policy.bindingsList);
167       setEtag(policy.etag);
168       setVersion(policy.version);
169     }
170 
171     /**
172      * Replaces the builder's map of bindings with the given map of bindings.
173      *
174      * @throws NullPointerException if the given map is null or contains any null keys or values
175      * @throws IllegalArgumentException if any identities in the given map are null or if policy
176      *     version is equal to 3 or has conditional bindings because conditional policies are not
177      *     supported
178      */
setBindings(Map<Role, Set<Identity>> bindings)179     public final Builder setBindings(Map<Role, Set<Identity>> bindings) {
180       checkNotNull(bindings, "The provided map of bindings cannot be null.");
181       checkArgument(
182           !isConditional(this.version, this.bindingsList),
183           "setBindings() is only supported with version 1 policies and non-conditional policies");
184       for (Map.Entry<Role, Set<Identity>> binding : bindings.entrySet()) {
185         checkNotNull(binding.getKey(), "The role cannot be null.");
186         Set<Identity> identities = binding.getValue();
187         checkNotNull(identities, "A role cannot be assigned to a null set of identities.");
188         checkArgument(!identities.contains(null), "Null identities are not permitted.");
189       }
190       // convert into List format
191       this.bindingsList.clear();
192       for (Map.Entry<Role, Set<Identity>> binding : bindings.entrySet()) {
193         Binding.Builder bindingBuilder = Binding.newBuilder();
194         bindingBuilder.setRole(binding.getKey().getValue());
195         ImmutableList.Builder<String> membersBuilder = ImmutableList.builder();
196         for (Identity identity : binding.getValue()) {
197           membersBuilder.add(identity.strValue());
198         }
199         bindingBuilder.setMembers(membersBuilder.build());
200         this.bindingsList.add(bindingBuilder.build());
201       }
202       return this;
203     }
204 
205     /**
206      * Replaces the builder's List of bindings with the given List of Bindings.
207      *
208      * @throws NullPointerException if the given list is null, role is null, or contains any null
209      *     members in bindings
210      */
setBindings(List<Binding> bindings)211     public final Builder setBindings(List<Binding> bindings) {
212       this.bindingsList.clear();
213       for (Binding binding : bindings) {
214         Binding.Builder bindingBuilder = Binding.newBuilder();
215         bindingBuilder.setMembers(ImmutableList.copyOf(binding.getMembers()));
216         bindingBuilder.setRole(binding.getRole());
217         bindingBuilder.setCondition(binding.getCondition());
218         this.bindingsList.add(bindingBuilder.build());
219       }
220       return this;
221     }
222 
223     /**
224      * Removes the role (and all identities associated with that role) from the policy.
225      *
226      * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings
227      *     because conditional policies are not supported
228      */
removeRole(Role role)229     public final Builder removeRole(Role role) {
230       checkArgument(
231           !isConditional(this.version, this.bindingsList),
232           "removeRole() is only supported with version 1 policies and non-conditional policies");
233       Iterator iterator = bindingsList.iterator();
234 
235       while (iterator.hasNext()) {
236         Binding binding = (Binding) iterator.next();
237         if (binding.getRole().equals(role.getValue())) {
238           iterator.remove();
239           return this;
240         }
241       }
242       return this;
243     }
244 
245     /**
246      * Adds one or more identities to the policy under the role specified.
247      *
248      * @throws NullPointerException if the role or any of the identities is null.
249      * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings.
250      */
addIdentity(Role role, Identity first, Identity... others)251     public final Builder addIdentity(Role role, Identity first, Identity... others) {
252       checkArgument(
253           !isConditional(this.version, this.bindingsList),
254           "addIdentity() is only supported with version 1 policies and non-conditional policies");
255       String nullIdentityMessage = "Null identities are not permitted.";
256       checkNotNull(first, nullIdentityMessage);
257       checkNotNull(others, nullIdentityMessage);
258       checkNotNull(role, "The role cannot be null.");
259       for (int i = 0; i < bindingsList.size(); i++) {
260         Binding binding = bindingsList.get(i);
261         if (binding.getRole().equals(role.getValue())) {
262           Binding.Builder bindingBuilder = binding.toBuilder();
263           ImmutableSet.Builder<String> membersBuilder = ImmutableSet.builder();
264           membersBuilder.addAll(binding.getMembers());
265           membersBuilder.add(first.strValue());
266           for (Identity identity : others) {
267             membersBuilder.add(identity.strValue());
268           }
269           bindingBuilder.setMembers(membersBuilder.build());
270           bindingsList.set(i, bindingBuilder.build());
271           return this;
272         }
273       }
274       // Binding does not yet exist.
275       Binding.Builder bindingBuilder = Binding.newBuilder().setRole(role.getValue());
276       ImmutableSet.Builder<String> membersBuilder = ImmutableSet.builder();
277       membersBuilder.add(first.strValue());
278       for (Identity identity : others) {
279         membersBuilder.add(identity.strValue());
280       }
281       bindingBuilder.setMembers(membersBuilder.build());
282       bindingsList.add(bindingBuilder.build());
283       return this;
284     }
285 
286     /**
287      * Removes one or more identities from an existing binding. Does nothing if the binding
288      * associated with the provided role doesn't exist.
289      *
290      * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings
291      */
removeIdentity(Role role, Identity first, Identity... others)292     public final Builder removeIdentity(Role role, Identity first, Identity... others) {
293       checkArgument(
294           !isConditional(this.version, this.bindingsList),
295           "removeIdentity() is only supported with version 1 policies and non-conditional policies");
296       String nullIdentityMessage = "Null identities are not permitted.";
297       checkNotNull(first, nullIdentityMessage);
298       checkNotNull(others, nullIdentityMessage);
299       checkNotNull(role, "The role cannot be null.");
300       for (int i = 0; i < bindingsList.size(); i++) {
301         Binding binding = bindingsList.get(i);
302         if (binding.getRole().equals(role.getValue())) {
303           Binding.Builder bindingBuilder = binding.toBuilder().removeMembers(first.strValue());
304           for (Identity identity : others) {
305             bindingBuilder.removeMembers(identity.strValue());
306           }
307           Binding updatedBindings = bindingBuilder.build();
308           bindingsList.set(i, updatedBindings);
309           break;
310         }
311       }
312 
313       Iterator iterator = bindingsList.iterator();
314       while (iterator.hasNext()) {
315         Binding binding = (Binding) iterator.next();
316         if (binding.getRole().equals(role.getValue()) && binding.getMembers().size() == 0) {
317           iterator.remove();
318           break;
319         }
320       }
321 
322       return this;
323     }
324 
325     /**
326      * Sets the policy's etag.
327      *
328      * <p>Etags are used for optimistic concurrency control as a way to help prevent simultaneous
329      * updates of a policy from overwriting each other. It is strongly suggested that systems make
330      * use of the etag in the read-modify-write cycle to perform policy updates in order to avoid
331      * race conditions. An etag is returned in the response to getIamPolicy, and systems are
332      * expected to put that etag in the request to setIamPolicy to ensure that their change will be
333      * applied to the same version of the policy. If no etag is provided in the call to
334      * setIamPolicy, then the existing policy is overwritten blindly.
335      */
setEtag(String etag)336     public final Builder setEtag(String etag) {
337       this.etag = etag;
338       return this;
339     }
340 
341     /** Sets the version of the policy. */
setVersion(int version)342     public final Builder setVersion(int version) {
343       this.version = version;
344       return this;
345     }
346 
347     /** Creates a {@code Policy} object. */
build()348     public final Policy build() {
349       return new Policy(this);
350     }
351   }
352 
Policy(Builder builder)353   private Policy(Builder builder) {
354     this.bindingsList = ImmutableList.copyOf(builder.bindingsList);
355     this.etag = builder.etag;
356     this.version = builder.version;
357   }
358 
359   /** Returns a builder containing the properties of this IAM Policy. */
toBuilder()360   public Builder toBuilder() {
361     return new Builder(this);
362   }
363 
364   /**
365    * Returns the map of bindings that comprises the policy.
366    *
367    * @throws IllegalArgumentException if policy version is equal to 3 or has conditional bindings
368    */
getBindings()369   public Map<Role, Set<Identity>> getBindings() {
370     checkArgument(
371         !isConditional(this.version, this.bindingsList),
372         "getBindings() is only supported with version 1 policies and non-conditional policies");
373     ImmutableMap.Builder<Role, Set<Identity>> bindingsV1Builder = ImmutableMap.builder();
374     for (Binding binding : bindingsList) {
375       ImmutableSet.Builder<Identity> identities = ImmutableSet.builder();
376       for (String member : binding.getMembers()) {
377         identities.add(Identity.valueOf(member));
378       }
379       bindingsV1Builder.put(Role.of(binding.getRole()), identities.build());
380     }
381     return bindingsV1Builder.build();
382   }
383 
384   /** Returns the list of bindings that comprises the policy for version 3. */
getBindingsList()385   public ImmutableList<Binding> getBindingsList() {
386     return bindingsList;
387   }
388 
389   /**
390    * Returns the policy's etag.
391    *
392    * <p>Etags are used for optimistic concurrency control as a way to help prevent simultaneous
393    * updates of a policy from overwriting each other. It is strongly suggested that systems make use
394    * of the etag in the read-modify-write cycle to perform policy updates in order to avoid race
395    * conditions. An etag is returned in the response to getIamPolicy, and systems are expected to
396    * put that etag in the request to setIamPolicy to ensure that their change will be applied to the
397    * same version of the policy. If no etag is provided in the call to setIamPolicy, then the
398    * existing policy is overwritten blindly.
399    */
getEtag()400   public String getEtag() {
401     return etag;
402   }
403 
404   /**
405    * Returns the version of the policy. The default version is 0, meaning only the "owner",
406    * "editor", and "viewer" roles are permitted. If the version is 1, you may also use other roles.
407    */
getVersion()408   public int getVersion() {
409     return version;
410   }
411 
412   @Override
toString()413   public String toString() {
414     return MoreObjects.toStringHelper(this)
415         .add("bindings", bindingsList)
416         .add("etag", etag)
417         .add("version", version)
418         .toString();
419   }
420 
421   @Override
hashCode()422   public int hashCode() {
423     return Objects.hash(getClass(), bindingsList, etag, version);
424   }
425 
426   @Override
equals(Object obj)427   public boolean equals(Object obj) {
428     if (obj == this) {
429       return true;
430     }
431     if (!(obj instanceof Policy)) {
432       return false;
433     }
434     Policy other = (Policy) obj;
435     if (bindingsList == null && other.getBindingsList() == null) {
436       return true;
437     }
438     if ((bindingsList == null && other.getBindingsList() != null)
439         || bindingsList != null && other.getBindingsList() == null
440         || bindingsList.size() != other.getBindingsList().size()) {
441       return false;
442     }
443     for (Binding binding : bindingsList) {
444       if (!other.getBindingsList().contains(binding)) {
445         return false;
446       }
447     }
448     return Objects.equals(etag, other.getEtag()) && version == other.getVersion();
449   }
450 
451   /** Returns a builder for {@code Policy} objects. */
newBuilder()452   public static Builder newBuilder() {
453     return new Builder();
454   }
455 }
456