Skip to content

Commit 5fbb41c

Browse files
committed
Merge pull request #760 from ajkannan/iam-docs-and-cleanup
IAM docs and functional methods for policies
2 parents fa64fa5 + 7bbe67f commit 5fbb41c

File tree

10 files changed

+322
-88
lines changed

10 files changed

+322
-88
lines changed

gcloud-java-core/src/main/java/com/google/gcloud/IamPolicy.java

Lines changed: 36 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,16 @@
1717
package com.google.gcloud;
1818

1919
import static com.google.common.base.Preconditions.checkArgument;
20+
import static com.google.common.base.Preconditions.checkNotNull;
2021

2122
import com.google.common.collect.ImmutableMap;
2223
import com.google.common.collect.ImmutableSet;
2324

2425
import java.io.Serializable;
2526
import java.util.Arrays;
26-
import java.util.Collection;
2727
import java.util.HashMap;
2828
import java.util.HashSet;
29-
import java.util.LinkedList;
30-
import java.util.List;
29+
import java.util.LinkedHashSet;
3130
import java.util.Map;
3231
import java.util.Objects;
3332
import java.util.Set;
@@ -69,12 +68,16 @@ protected Builder() {}
6968
/**
7069
* Replaces the builder's map of bindings with the given map of bindings.
7170
*
72-
* @throws IllegalArgumentException if the provided map is null or contain any null values
71+
* @throws NullPointerException if the given map is null or contains any null keys or values
72+
* @throws IllegalArgumentException if any identities in the given map are null
7373
*/
7474
public final B bindings(Map<R, Set<Identity>> bindings) {
75-
checkArgument(bindings != null, "The provided map of bindings cannot be null.");
75+
checkNotNull(bindings, "The provided map of bindings cannot be null.");
7676
for (Map.Entry<R, Set<Identity>> binding : bindings.entrySet()) {
77-
verifyBinding(binding.getKey(), binding.getValue());
77+
checkNotNull(binding.getKey(), "The role cannot be null.");
78+
Set<Identity> identities = binding.getValue();
79+
checkNotNull(identities, "A role cannot be assigned to a null set of identities.");
80+
checkArgument(!identities.contains(null), "Null identities are not permitted.");
7881
}
7982
this.bindings.clear();
8083
for (Map.Entry<R, Set<Identity>> binding : bindings.entrySet()) {
@@ -84,78 +87,50 @@ public final B bindings(Map<R, Set<Identity>> bindings) {
8487
}
8588

8689
/**
87-
* Adds a binding to the policy.
88-
*
89-
* @throws IllegalArgumentException if the policy already contains a binding with the same role
90-
* or if the role or any identities are null
91-
*/
92-
public final B addBinding(R role, Set<Identity> identities) {
93-
verifyBinding(role, identities);
94-
checkArgument(!bindings.containsKey(role),
95-
"The policy already contains a binding with the role " + role.toString() + ".");
96-
bindings.put(role, new HashSet<Identity>(identities));
97-
return self();
98-
}
99-
100-
/**
101-
* Adds a binding to the policy.
102-
*
103-
* @throws IllegalArgumentException if the policy already contains a binding with the same role
104-
* or if the role or any identities are null
105-
*/
106-
public final B addBinding(R role, Identity first, Identity... others) {
107-
HashSet<Identity> identities = new HashSet<>();
108-
identities.add(first);
109-
identities.addAll(Arrays.asList(others));
110-
return addBinding(role, identities);
111-
}
112-
113-
private void verifyBinding(R role, Collection<Identity> identities) {
114-
checkArgument(role != null, "The role cannot be null.");
115-
verifyIdentities(identities);
116-
}
117-
118-
private void verifyIdentities(Collection<Identity> identities) {
119-
checkArgument(identities != null, "A role cannot be assigned to a null set of identities.");
120-
checkArgument(!identities.contains(null), "Null identities are not permitted.");
121-
}
122-
123-
/**
124-
* Removes the binding associated with the specified role.
90+
* Removes the role (and all identities associated with that role) from the policy.
12591
*/
126-
public final B removeBinding(R role) {
92+
public final B removeRole(R role) {
12793
bindings.remove(role);
12894
return self();
12995
}
13096

13197
/**
132-
* Adds one or more identities to an existing binding.
98+
* Adds one or more identities to the policy under the role specified.
13399
*
134-
* @throws IllegalArgumentException if the policy doesn't contain a binding with the specified
135-
* role or any identities are null
100+
* @throws NullPointerException if the role or any of the identities is null.
136101
*/
137102
public final B addIdentity(R role, Identity first, Identity... others) {
138-
checkArgument(bindings.containsKey(role),
139-
"The policy doesn't contain the role " + role.toString() + ".");
140-
List<Identity> toAdd = new LinkedList<>();
103+
String nullIdentityMessage = "Null identities are not permitted.";
104+
checkNotNull(first, nullIdentityMessage);
105+
checkNotNull(others, nullIdentityMessage);
106+
for (Identity identity : others) {
107+
checkNotNull(identity, nullIdentityMessage);
108+
}
109+
Set<Identity> toAdd = new LinkedHashSet<>();
141110
toAdd.add(first);
142111
toAdd.addAll(Arrays.asList(others));
143-
verifyIdentities(toAdd);
144-
bindings.get(role).addAll(toAdd);
112+
Set<Identity> identities = bindings.get(checkNotNull(role, "The role cannot be null."));
113+
if (identities == null) {
114+
identities = new HashSet<Identity>();
115+
bindings.put(role, identities);
116+
}
117+
identities.addAll(toAdd);
145118
return self();
146119
}
147120

148121
/**
149-
* Removes one or more identities from an existing binding.
150-
*
151-
* @throws IllegalArgumentException if the policy doesn't contain a binding with the specified
152-
* role
122+
* Removes one or more identities from an existing binding. Does nothing if the binding
123+
* associated with the provided role doesn't exist.
153124
*/
154125
public final B removeIdentity(R role, Identity first, Identity... others) {
155-
checkArgument(bindings.containsKey(role),
156-
"The policy doesn't contain the role " + role.toString() + ".");
157-
bindings.get(role).remove(first);
158-
bindings.get(role).removeAll(Arrays.asList(others));
126+
Set<Identity> identities = bindings.get(role);
127+
if (identities != null) {
128+
identities.remove(first);
129+
identities.removeAll(Arrays.asList(others));
130+
}
131+
if (identities != null && identities.isEmpty()) {
132+
bindings.remove(role);
133+
}
159134
return self();
160135
}
161136

gcloud-java-core/src/test/java/com/google/gcloud/IamPolicyTest.java

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828

2929
import org.junit.Test;
3030

31+
import java.util.HashMap;
32+
import java.util.HashSet;
3133
import java.util.Map;
3234
import java.util.Set;
3335

@@ -46,8 +48,8 @@ public class IamPolicyTest {
4648
"editor",
4749
ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN));
4850
private static final PolicyImpl SIMPLE_POLICY = PolicyImpl.builder()
49-
.addBinding("viewer", ImmutableSet.of(USER, SERVICE_ACCOUNT, ALL_USERS))
50-
.addBinding("editor", ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN))
51+
.addIdentity("viewer", USER, SERVICE_ACCOUNT, ALL_USERS)
52+
.addIdentity("editor", ALL_AUTH_USERS, GROUP, DOMAIN)
5153
.build();
5254
private static final PolicyImpl FULL_POLICY =
5355
new PolicyImpl.Builder(SIMPLE_POLICY.bindings(), "etag", 1).build();
@@ -93,7 +95,7 @@ public void testBuilder() {
9395
assertEquals(editorBinding, policy.bindings());
9496
assertEquals("etag", policy.etag());
9597
assertEquals(1, policy.version().intValue());
96-
policy = SIMPLE_POLICY.toBuilder().removeBinding("editor").build();
98+
policy = SIMPLE_POLICY.toBuilder().removeRole("editor").build();
9799
assertEquals(ImmutableMap.of("viewer", BINDINGS.get("viewer")), policy.bindings());
98100
assertNull(policy.etag());
99101
assertNull(policy.version());
@@ -105,22 +107,61 @@ public void testBuilder() {
105107
policy.bindings());
106108
assertNull(policy.etag());
107109
assertNull(policy.version());
108-
policy = PolicyImpl.builder().addBinding("owner", USER, SERVICE_ACCOUNT).build();
110+
policy = PolicyImpl.builder()
111+
.removeIdentity("viewer", USER)
112+
.addIdentity("owner", USER, SERVICE_ACCOUNT)
113+
.addIdentity("editor", GROUP)
114+
.removeIdentity("editor", GROUP)
115+
.build();
109116
assertEquals(
110117
ImmutableMap.of("owner", ImmutableSet.of(USER, SERVICE_ACCOUNT)), policy.bindings());
111118
assertNull(policy.etag());
112119
assertNull(policy.version());
120+
}
121+
122+
@Test
123+
public void testIllegalPolicies() {
124+
try {
125+
PolicyImpl.builder().addIdentity(null, USER);
126+
fail("Null role should cause exception.");
127+
} catch (NullPointerException ex) {
128+
assertEquals("The role cannot be null.", ex.getMessage());
129+
}
130+
try {
131+
PolicyImpl.builder().addIdentity("viewer", null, USER);
132+
fail("Null identity should cause exception.");
133+
} catch (NullPointerException ex) {
134+
assertEquals("Null identities are not permitted.", ex.getMessage());
135+
}
136+
try {
137+
PolicyImpl.builder().addIdentity("viewer", USER, (Identity[]) null);
138+
fail("Null identity should cause exception.");
139+
} catch (NullPointerException ex) {
140+
assertEquals("Null identities are not permitted.", ex.getMessage());
141+
}
142+
try {
143+
PolicyImpl.builder().bindings(null);
144+
fail("Null bindings map should cause exception.");
145+
} catch (NullPointerException ex) {
146+
assertEquals("The provided map of bindings cannot be null.", ex.getMessage());
147+
}
113148
try {
114-
SIMPLE_POLICY.toBuilder().addBinding("viewer", USER);
115-
fail("Should have failed due to duplicate role.");
116-
} catch (IllegalArgumentException e) {
117-
assertEquals("The policy already contains a binding with the role viewer.", e.getMessage());
149+
Map<String, Set<Identity>> bindings = new HashMap<>();
150+
bindings.put("viewer", null);
151+
PolicyImpl.builder().bindings(bindings);
152+
fail("Null set of identities should cause exception.");
153+
} catch (NullPointerException ex) {
154+
assertEquals("A role cannot be assigned to a null set of identities.", ex.getMessage());
118155
}
119156
try {
120-
SIMPLE_POLICY.toBuilder().addBinding("editor", ImmutableSet.of(USER));
121-
fail("Should have failed due to duplicate role.");
122-
} catch (IllegalArgumentException e) {
123-
assertEquals("The policy already contains a binding with the role editor.", e.getMessage());
157+
Map<String, Set<Identity>> bindings = new HashMap<>();
158+
Set<Identity> identities = new HashSet<>();
159+
identities.add(null);
160+
bindings.put("viewer", identities);
161+
PolicyImpl.builder().bindings(bindings);
162+
fail("Null identity should cause exception.");
163+
} catch (IllegalArgumentException ex) {
164+
assertEquals("Null identities are not permitted.", ex.getMessage());
124165
}
125166
}
126167

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2016 Google Inc. All Rights Reserved.
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+
/*
18+
* EDITING INSTRUCTIONS
19+
* This file is referenced in READMEs and javadoc. Any change to this file should be reflected in
20+
* the project's READMEs and package-info.java.
21+
*/
22+
23+
package com.google.gcloud.examples.resourcemanager.snippets;
24+
25+
import com.google.gcloud.Identity;
26+
import com.google.gcloud.resourcemanager.Policy;
27+
import com.google.gcloud.resourcemanager.Policy.Role;
28+
import com.google.gcloud.resourcemanager.Project;
29+
import com.google.gcloud.resourcemanager.ResourceManager;
30+
import com.google.gcloud.resourcemanager.ResourceManagerOptions;
31+
32+
/**
33+
* A snippet for Google Cloud Resource Manager showing how to modify a project's IAM policy.
34+
*/
35+
public class ModifyPolicy {
36+
37+
public static void main(String... args) {
38+
// Create Resource Manager service object
39+
// By default, credentials are inferred from the runtime environment.
40+
ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service();
41+
42+
// Get a project from the server
43+
String projectId = "some-project-id"; // Use an existing project's ID
44+
Project project = resourceManager.get(projectId);
45+
46+
// Get the project's policy
47+
Policy policy = project.getPolicy();
48+
49+
// Add a viewer
50+
Policy.Builder modifiedPolicy = policy.toBuilder();
51+
Identity newViewer = Identity.user("<insert user's email address here>");
52+
modifiedPolicy.addIdentity(Role.viewer(), newViewer);
53+
54+
// Write policy
55+
Policy updatedPolicy = project.replacePolicy(modifiedPolicy.build());
56+
57+
// Print policy
58+
System.out.printf("Updated policy for %s: %n%s%n", projectId, updatedPolicy);
59+
}
60+
}

gcloud-java-resourcemanager/README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,46 @@ while (projectIterator.hasNext()) {
163163
}
164164
```
165165

166+
#### Managing IAM Policies
167+
You can edit [Google Cloud IAM](https://cloud.google.com/iam/) (Identity and Access Management)
168+
policies on the project-level using this library as well. We recommend using the read-modify-write
169+
pattern to make policy changes. This entails reading the project's current policy, updating it
170+
locally, and then sending the modified policy for writing, as shown in the snippet below. First,
171+
add these imports:
172+
173+
```java
174+
import com.google.gcloud.Identity;
175+
import com.google.gcloud.resourcemanager.Policy;
176+
import com.google.gcloud.resourcemanager.Policy.Role;
177+
```
178+
179+
Assuming you have completed the steps above to create the `ResourceManager` service object and load
180+
a project from the server, you just need to add the following code:
181+
182+
```java
183+
// Get the project's policy
184+
Policy policy = project.getPolicy();
185+
186+
// Add a viewer
187+
Policy.Builder modifiedPolicy = policy.toBuilder();
188+
Identity newViewer = Identity.user("<insert user's email address here>");
189+
if (policy.bindings().containsKey(Role.viewer())) {
190+
modifiedPolicy.addIdentity(Role.viewer(), newViewer);
191+
} else {
192+
modifiedPolicy.addBinding(Role.viewer(), newViewer);
193+
}
194+
195+
// Write policy
196+
Policy updatedPolicy = project.replacePolicy(modifiedPolicy.build());
197+
```
198+
199+
Note that the policy you pass in to `replacePolicy` overwrites the original policy. For example, if
200+
the original policy has two bindings and you call `replacePolicy` with a new policy containing only
201+
one binding, the two original bindings are lost.
202+
166203
#### Complete source code
167204

168-
We put together all the code shown above into two programs. Both programs assume that you are
205+
We put together all the code shown above into three programs. The programs assume that you are
169206
running from your own desktop and used the Google Cloud SDK to authenticate yourself.
170207

171208
The first program creates a project if it does not exist. Complete source code can be found at
@@ -175,6 +212,10 @@ The second program updates a project if it exists and lists all projects the use
175212
view. Complete source code can be found at
176213
[UpdateAndListProjects.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/resourcemanager/snippets/UpdateAndListProjects.java).
177214

215+
The third program modifies the IAM policy associated with a project using the read-modify-write
216+
pattern. Complete source code can be found at
217+
[ModifyPolicy.java](../gcloud-java-examples/src/main/java/com/google/gcloud/examples/resourcemanager/snippets/ModifyPolicy.java)
218+
178219
Java Versions
179220
-------------
180221

gcloud-java-resourcemanager/src/main/java/com/google/gcloud/resourcemanager/Policy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ public Builder toBuilder() {
178178
return new Builder(bindings(), etag(), version());
179179
}
180180

181+
@Override
182+
public String toString() {
183+
return toPb().toString();
184+
}
185+
181186
com.google.api.services.cloudresourcemanager.model.Policy toPb() {
182187
com.google.api.services.cloudresourcemanager.model.Policy policyPb =
183188
new com.google.api.services.cloudresourcemanager.model.Policy();

0 commit comments

Comments
 (0)