Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IAM docs and functional methods for policies #760

Merged
merged 3 commits into from
Mar 21, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 36 additions & 61 deletions gcloud-java-core/src/main/java/com/google/gcloud/IamPolicy.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,16 @@
package com.google.gcloud;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
Expand Down Expand Up @@ -69,12 +68,16 @@ protected Builder() {}
/**
* Replaces the builder's map of bindings with the given map of bindings.
*
* @throws IllegalArgumentException if the provided map is null or contain any null values
* @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
*/
public final B bindings(Map<R, Set<Identity>> bindings) {
checkArgument(bindings != null, "The provided map of bindings cannot be null.");
checkNotNull(bindings, "The provided map of bindings cannot be null.");
for (Map.Entry<R, Set<Identity>> binding : bindings.entrySet()) {
verifyBinding(binding.getKey(), binding.getValue());
checkNotNull(binding.getKey(), "The role cannot be null.");
Set<Identity> 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();
for (Map.Entry<R, Set<Identity>> binding : bindings.entrySet()) {
Expand All @@ -84,78 +87,50 @@ public final B bindings(Map<R, Set<Identity>> bindings) {
}

/**
* Adds a binding to the policy.
*
* @throws IllegalArgumentException if the policy already contains a binding with the same role
* or if the role or any identities are null
*/
public final B addBinding(R role, Set<Identity> identities) {
verifyBinding(role, identities);
checkArgument(!bindings.containsKey(role),
"The policy already contains a binding with the role " + role.toString() + ".");
bindings.put(role, new HashSet<Identity>(identities));
return self();
}

/**
* Adds a binding to the policy.
*
* @throws IllegalArgumentException if the policy already contains a binding with the same role
* or if the role or any identities are null
*/
public final B addBinding(R role, Identity first, Identity... others) {
HashSet<Identity> identities = new HashSet<>();
identities.add(first);
identities.addAll(Arrays.asList(others));
return addBinding(role, identities);
}

private void verifyBinding(R role, Collection<Identity> identities) {
checkArgument(role != null, "The role cannot be null.");
verifyIdentities(identities);
}

private void verifyIdentities(Collection<Identity> identities) {
checkArgument(identities != null, "A role cannot be assigned to a null set of identities.");
checkArgument(!identities.contains(null), "Null identities are not permitted.");
}

/**
* Removes the binding associated with the specified role.
* Removes the role (and all identities associated with that role) from the policy.
*/
public final B removeBinding(R role) {
public final B removeRole(R role) {
bindings.remove(role);
return self();
}

/**
* Adds one or more identities to an existing binding.
* Adds one or more identities to the policy under the role specified.
*
* @throws IllegalArgumentException if the policy doesn't contain a binding with the specified
* role or any identities are null
* @throws NullPointerException if the role or any of the identities is null.
*/
public final B addIdentity(R role, Identity first, Identity... others) {
checkArgument(bindings.containsKey(role),
"The policy doesn't contain the role " + role.toString() + ".");
List<Identity> toAdd = new LinkedList<>();
String nullIdentityMessage = "Null identities are not permitted.";
checkNotNull(first, nullIdentityMessage);
checkNotNull(others, nullIdentityMessage);
for (Identity identity : others) {
checkNotNull(identity, nullIdentityMessage);
}
Set<Identity> toAdd = new LinkedHashSet<>();
toAdd.add(first);
toAdd.addAll(Arrays.asList(others));
verifyIdentities(toAdd);
bindings.get(role).addAll(toAdd);
Set<Identity> identities = bindings.get(checkNotNull(role, "The role cannot be null."));
if (identities == null) {
identities = new HashSet<Identity>();
bindings.put(role, identities);
}
identities.addAll(toAdd);
return self();
}

/**
* Removes one or more identities from an existing binding.
*
* @throws IllegalArgumentException if the policy doesn't contain a binding with the specified
* role
* Removes one or more identities from an existing binding. Does nothing if the binding
* associated with the provided role doesn't exist.
*/
public final B removeIdentity(R role, Identity first, Identity... others) {
checkArgument(bindings.containsKey(role),
"The policy doesn't contain the role " + role.toString() + ".");
bindings.get(role).remove(first);
bindings.get(role).removeAll(Arrays.asList(others));
Set<Identity> identities = bindings.get(role);
if (identities != null) {
identities.remove(first);
identities.removeAll(Arrays.asList(others));
}
if (identities != null && identities.isEmpty()) {
bindings.remove(role);
}
return self();
}

Expand Down
65 changes: 53 additions & 12 deletions gcloud-java-core/src/test/java/com/google/gcloud/IamPolicyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

import org.junit.Test;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

Expand All @@ -46,8 +48,8 @@ public class IamPolicyTest {
"editor",
ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN));
private static final PolicyImpl SIMPLE_POLICY = PolicyImpl.builder()
.addBinding("viewer", ImmutableSet.of(USER, SERVICE_ACCOUNT, ALL_USERS))
.addBinding("editor", ImmutableSet.of(ALL_AUTH_USERS, GROUP, DOMAIN))
.addIdentity("viewer", USER, SERVICE_ACCOUNT, ALL_USERS)
.addIdentity("editor", ALL_AUTH_USERS, GROUP, DOMAIN)
.build();
private static final PolicyImpl FULL_POLICY =
new PolicyImpl.Builder(SIMPLE_POLICY.bindings(), "etag", 1).build();
Expand Down Expand Up @@ -93,7 +95,7 @@ public void testBuilder() {
assertEquals(editorBinding, policy.bindings());
assertEquals("etag", policy.etag());
assertEquals(1, policy.version().intValue());
policy = SIMPLE_POLICY.toBuilder().removeBinding("editor").build();
policy = SIMPLE_POLICY.toBuilder().removeRole("editor").build();
assertEquals(ImmutableMap.of("viewer", BINDINGS.get("viewer")), policy.bindings());
assertNull(policy.etag());
assertNull(policy.version());
Expand All @@ -105,22 +107,61 @@ public void testBuilder() {
policy.bindings());
assertNull(policy.etag());
assertNull(policy.version());
policy = PolicyImpl.builder().addBinding("owner", USER, SERVICE_ACCOUNT).build();
policy = PolicyImpl.builder()
.removeIdentity("viewer", USER)
.addIdentity("owner", USER, SERVICE_ACCOUNT)
.addIdentity("editor", GROUP)
.removeIdentity("editor", GROUP)
.build();
assertEquals(
ImmutableMap.of("owner", ImmutableSet.of(USER, SERVICE_ACCOUNT)), policy.bindings());
assertNull(policy.etag());
assertNull(policy.version());
}

@Test
public void testIllegalPolicies() {
try {
PolicyImpl.builder().addIdentity(null, USER);
fail("Null role should cause exception.");
} catch (NullPointerException ex) {
assertEquals("The role cannot be null.", ex.getMessage());
}
try {
PolicyImpl.builder().addIdentity("viewer", null, USER);
fail("Null identity should cause exception.");
} catch (NullPointerException ex) {
assertEquals("Null identities are not permitted.", ex.getMessage());
}
try {
PolicyImpl.builder().addIdentity("viewer", USER, (Identity[]) null);
fail("Null identity should cause exception.");
} catch (NullPointerException ex) {
assertEquals("Null identities are not permitted.", ex.getMessage());
}
try {
PolicyImpl.builder().bindings(null);
fail("Null bindings map should cause exception.");
} catch (NullPointerException ex) {
assertEquals("The provided map of bindings cannot be null.", ex.getMessage());
}
try {
SIMPLE_POLICY.toBuilder().addBinding("viewer", USER);
fail("Should have failed due to duplicate role.");
} catch (IllegalArgumentException e) {
assertEquals("The policy already contains a binding with the role viewer.", e.getMessage());
Map<String, Set<Identity>> bindings = new HashMap<>();
bindings.put("viewer", null);
PolicyImpl.builder().bindings(bindings);
fail("Null set of identities should cause exception.");
} catch (NullPointerException ex) {
assertEquals("A role cannot be assigned to a null set of identities.", ex.getMessage());
}
try {
SIMPLE_POLICY.toBuilder().addBinding("editor", ImmutableSet.of(USER));
fail("Should have failed due to duplicate role.");
} catch (IllegalArgumentException e) {
assertEquals("The policy already contains a binding with the role editor.", e.getMessage());
Map<String, Set<Identity>> bindings = new HashMap<>();
Set<Identity> identities = new HashSet<>();
identities.add(null);
bindings.put("viewer", identities);
PolicyImpl.builder().bindings(bindings);
fail("Null identity should cause exception.");
} catch (IllegalArgumentException ex) {
assertEquals("Null identities are not permitted.", ex.getMessage());
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2016 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.
*/

/*
* EDITING INSTRUCTIONS
* This file is referenced in READMEs and javadoc. Any change to this file should be reflected in
* the project's READMEs and package-info.java.
*/

package com.google.gcloud.examples.resourcemanager.snippets;

import com.google.gcloud.Identity;
import com.google.gcloud.resourcemanager.Policy;
import com.google.gcloud.resourcemanager.Policy.Role;
import com.google.gcloud.resourcemanager.Project;
import com.google.gcloud.resourcemanager.ResourceManager;
import com.google.gcloud.resourcemanager.ResourceManagerOptions;

/**
* A snippet for Google Cloud Resource Manager showing how to modify a project's IAM policy.
*/
public class ModifyPolicy {

public static void main(String... args) {
// Create Resource Manager service object
// By default, credentials are inferred from the runtime environment.
ResourceManager resourceManager = ResourceManagerOptions.defaultInstance().service();

// Get a project from the server
String projectId = "some-project-id"; // Use an existing project's ID
Project project = resourceManager.get(projectId);

// Get the project's policy
Policy policy = project.getPolicy();

// Add a viewer
Policy.Builder modifiedPolicy = policy.toBuilder();
Identity newViewer = Identity.user("<insert user's email address here>");
modifiedPolicy.addIdentity(Role.viewer(), newViewer);

// Write policy
Policy updatedPolicy = project.replacePolicy(modifiedPolicy.build());

// Print policy
System.out.printf("Updated policy for %s: %n%s%n", projectId, updatedPolicy);
}
}
43 changes: 42 additions & 1 deletion gcloud-java-resourcemanager/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,46 @@ while (projectIterator.hasNext()) {
}
```

#### Managing IAM Policies
You can edit [Google Cloud IAM](https://cloud.google.com/iam/) (Identity and Access Management)
policies on the project-level using this library as well. We recommend using the read-modify-write
pattern to make policy changes. This entails reading the project's current policy, updating it
locally, and then sending the modified policy for writing, as shown in the snippet below. First,
add these imports:

```java
import com.google.gcloud.Identity;
import com.google.gcloud.resourcemanager.Policy;
import com.google.gcloud.resourcemanager.Policy.Role;
```

Assuming you have completed the steps above to create the `ResourceManager` service object and load
a project from the server, you just need to add the following code:

```java
// Get the project's policy
Policy policy = project.getPolicy();

// Add a viewer
Policy.Builder modifiedPolicy = policy.toBuilder();
Identity newViewer = Identity.user("<insert user's email address here>");
if (policy.bindings().containsKey(Role.viewer())) {
modifiedPolicy.addIdentity(Role.viewer(), newViewer);
} else {
modifiedPolicy.addBinding(Role.viewer(), newViewer);
}

// Write policy
Policy updatedPolicy = project.replacePolicy(modifiedPolicy.build());
```

Note that the policy you pass in to `replacePolicy` overwrites the original policy. For example, if
the original policy has two bindings and you call `replacePolicy` with a new policy containing only
one binding, the two original bindings are lost.

#### Complete source code

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

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

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

Java Versions
-------------

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ public Builder toBuilder() {
return new Builder(bindings(), etag(), version());
}

@Override
public String toString() {
return toPb().toString();
}

com.google.api.services.cloudresourcemanager.model.Policy toPb() {
com.google.api.services.cloudresourcemanager.model.Policy policyPb =
new com.google.api.services.cloudresourcemanager.model.Policy();
Expand Down
Loading