Skip to content

Commit

Permalink
Refactor IAM-related classes to be ready to support more services (#1224
Browse files Browse the repository at this point in the history
)

Refactor IAM-related classes to be ready to support more services
  - Remove resourcemanager's Policy class
  - Rename core's IamPolicy to Policy
  - Add Role class to hold string values for roles
  - Add Policy.Marshaller and Policy.DefaultMarshaller classes to convert Policy to/from gRPC protos
  - Add PolicyMarshaller to resourcemanager to convert Policy to/from resourcemanager's protos
  - Update READMEs, javadoc and examples 
  - Add factory methods for primitive roles, other minor fixes
  • Loading branch information
mziccard authored and aozarov committed Sep 6, 2016
1 parent 8cf6a66 commit a493457
Show file tree
Hide file tree
Showing 20 changed files with 575 additions and 456 deletions.
7 changes: 6 additions & 1 deletion google-cloud-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,12 @@
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>grpc-google-common-protos</artifactId>
<version>0.0.7</version>
<version>0.0.9</version>
</dependency>
<dependency>
<groupId>com.google.api.grpc</groupId>
<artifactId>grpc-google-iam-v1</artifactId>
<version>0.0.9</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
import java.util.Objects;

/**
* An identity in an {@link IamPolicy}. The following types of identities are permitted in IAM
* policies:
* An identity in a {@link Policy}. The following types of identities are permitted in IAM policies:
* <ul>
* <li>Google account
* <li>Service account
Expand Down Expand Up @@ -161,6 +160,11 @@ public static Identity domain(String domain) {
return new Identity(Type.DOMAIN, checkNotNull(domain));
}

@Override
public String toString() {
return strValue();
}

@Override
public int hashCode() {
return Objects.hash(value, type);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,87 +19,154 @@
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

import com.google.common.base.Function;
import com.google.common.base.MoreObjects;
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 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.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

/**
* Base 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 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.
* 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
* 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.
*
* @param <R> the data type of roles (should be serializable)
* @see <a href="https://cloud.google.com/iam/reference/rest/v1/Policy">Policy</a>
*/
public abstract class IamPolicy<R> implements Serializable {
public final class Policy implements Serializable {

private static final long serialVersionUID = 1114489978726897720L;
private static final long serialVersionUID = -3348914530232544290L;

private final Map<R, Set<Identity>> bindings;
private final Map<Role, Set<Identity>> bindings;
private final String etag;
private final Integer version;
private final int version;

public abstract static class Marshaller<T> {

protected static final Function<String, Identity> IDENTITY_VALUE_OF_FUNCTION =
new Function<String, Identity>() {
@Override
public Identity apply(String identityPb) {
return Identity.valueOf(identityPb);
}
};
protected static final Function<Identity, String> IDENTITY_STR_VALUE_FUNCTION =
new Function<Identity, String>() {
@Override
public String apply(Identity identity) {
return identity.strValue();
}
};

protected abstract Policy fromPb(T policyPb);

protected abstract T toPb(Policy policy);
}

public static class DefaultMarshaller extends Marshaller<com.google.iam.v1.Policy> {

@Override
protected Policy fromPb(com.google.iam.v1.Policy policyPb) {
Map<Role, Set<Identity>> bindings = new HashMap<>();
for (com.google.iam.v1.Binding bindingPb : policyPb.getBindingsList()) {
bindings.put(Role.of(bindingPb.getRole()),
ImmutableSet.copyOf(
Lists.transform(bindingPb.getMembersList(), IDENTITY_VALUE_OF_FUNCTION)));
}
return builder()
.bindings(bindings)
.etag(policyPb.getEtag().isEmpty() ? null
: BaseEncoding.base64().encode(policyPb.getEtag().toByteArray()))
.version(policyPb.getVersion())
.build();
}

@Override
protected com.google.iam.v1.Policy toPb(Policy policy) {
com.google.iam.v1.Policy.Builder policyBuilder = com.google.iam.v1.Policy.newBuilder();
List<com.google.iam.v1.Binding> bindingPbList = new LinkedList<>();
for (Map.Entry<Role, Set<Identity>> binding : policy.bindings().entrySet()) {
com.google.iam.v1.Binding.Builder bindingBuilder = com.google.iam.v1.Binding.newBuilder();
bindingBuilder.setRole(binding.getKey().value());
bindingBuilder.addAllMembers(
Lists.transform(new ArrayList<>(binding.getValue()), IDENTITY_STR_VALUE_FUNCTION));
bindingPbList.add(bindingBuilder.build());
}
policyBuilder.addAllBindings(bindingPbList);
if (policy.etag != null) {
policyBuilder.setEtag(ByteString.copyFrom(BaseEncoding.base64().decode(policy.etag)));
}
policyBuilder.setVersion(policy.version);
return policyBuilder.build();
}
}

/**
* Builder for an IAM Policy.
*
* @param <R> the data type of roles
* @param <B> the subclass extending this abstract builder
* A builder for {@code Policy} objects.
*/
public abstract static class Builder<R, B extends Builder<R, B>> {
public static class Builder {

private final Map<R, Set<Identity>> bindings = new HashMap<>();
private final Map<Role, Set<Identity>> bindings = new HashMap<>();
private String etag;
private Integer version;
private int version;

/**
* Constructor for IAM Policy builder.
*/
protected Builder() {}

protected Builder(Policy policy) {
bindings(policy.bindings);
etag(policy.etag);
version(policy.version);
}

/**
* 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
*/
public final B bindings(Map<R, Set<Identity>> bindings) {
public final Builder bindings(Map<Role, Set<Identity>> bindings) {
checkNotNull(bindings, "The provided map of bindings cannot be null.");
for (Map.Entry<R, Set<Identity>> binding : bindings.entrySet()) {
for (Map.Entry<Role, Set<Identity>> binding : bindings.entrySet()) {
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()) {
this.bindings.put(binding.getKey(), new HashSet<Identity>(binding.getValue()));
for (Map.Entry<Role, Set<Identity>> binding : bindings.entrySet()) {
this.bindings.put(binding.getKey(), new HashSet<>(binding.getValue()));
}
return self();
return this;
}

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

/**
* Adds one or more identities to the policy under the role specified.
*
* @throws NullPointerException if the role or any of the identities is null.
*/
public final B addIdentity(R role, Identity first, Identity... others) {
public final Builder addIdentity(Role role, Identity first, Identity... others) {
String nullIdentityMessage = "Null identities are not permitted.";
checkNotNull(first, nullIdentityMessage);
checkNotNull(others, nullIdentityMessage);
Expand All @@ -111,18 +178,18 @@ public final B addIdentity(R role, Identity first, Identity... others) {
toAdd.addAll(Arrays.asList(others));
Set<Identity> identities = bindings.get(checkNotNull(role, "The role cannot be null."));
if (identities == null) {
identities = new HashSet<Identity>();
identities = new HashSet<>();
bindings.put(role, identities);
}
identities.addAll(toAdd);
return self();
return this;
}

/**
* 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) {
public final Builder removeIdentity(Role role, Identity first, Identity... others) {
Set<Identity> identities = bindings.get(role);
if (identities != null) {
identities.remove(first);
Expand All @@ -131,7 +198,7 @@ public final B removeIdentity(R role, Identity first, Identity... others) {
if (identities != null && identities.isEmpty()) {
bindings.remove(role);
}
return self();
return this;
}

/**
Expand All @@ -145,31 +212,31 @@ public final B removeIdentity(R role, Identity first, Identity... others) {
* 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 B etag(String etag) {
protected final Builder etag(String etag) {
this.etag = etag;
return self();
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 B version(Integer version) {
protected final Builder version(int version) {
this.version = version;
return self();
return this;
}

@SuppressWarnings("unchecked")
private B self() {
return (B) this;
/**
* Creates a {@code Policy} object.
*/
public final Policy build() {
return new Policy(this);
}

public abstract IamPolicy<R> build();
}

protected IamPolicy(Builder<R, ? extends Builder<R, ?>> builder) {
ImmutableMap.Builder<R, Set<Identity>> bindingsBuilder = ImmutableMap.builder();
for (Map.Entry<R, Set<Identity>> binding : builder.bindings.entrySet()) {
private Policy(Builder builder) {
ImmutableMap.Builder<Role, Set<Identity>> bindingsBuilder = ImmutableMap.builder();
for (Map.Entry<Role, Set<Identity>> binding : builder.bindings.entrySet()) {
bindingsBuilder.put(binding.getKey(), ImmutableSet.copyOf(binding.getValue()));
}
this.bindings = bindingsBuilder.build();
Expand All @@ -180,17 +247,19 @@ protected IamPolicy(Builder<R, ? extends Builder<R, ?>> builder) {
/**
* Returns a builder containing the properties of this IAM Policy.
*/
public abstract Builder<R, ? extends Builder<R, ?>> toBuilder();
public Builder toBuilder() {
return new Builder(this);
}

/**
* The map of bindings that comprises the policy.
* Returns the map of bindings that comprises the policy.
*/
public Map<R, Set<Identity>> bindings() {
public Map<Role, Set<Identity>> bindings() {
return bindings;
}

/**
* The policy's etag.
* Returns the policy's etag.
*
* <p>Etags are used for optimistic concurrency control as a way to help prevent simultaneous
* updates of a policy from overwriting each other. It is strongly suggested that systems make
Expand All @@ -205,30 +274,45 @@ public String etag() {
}

/**
* 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.
* Returns 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.
*/
public Integer version() {
public int version() {
return version;
}

@Override
public final int hashCode() {
public String toString() {
return MoreObjects.toStringHelper(this)
.add("bindings", bindings)
.add("etag", etag)
.add("version", version)
.toString();
}

@Override
public int hashCode() {
return Objects.hash(getClass(), bindings, etag, version);
}

@Override
public final boolean equals(Object obj) {
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj == null || !obj.getClass().equals(getClass())) {
if (!(obj instanceof Policy)) {
return false;
}
@SuppressWarnings("rawtypes")
IamPolicy other = (IamPolicy) obj;
Policy other = (Policy) obj;
return Objects.equals(bindings, other.bindings())
&& Objects.equals(etag, other.etag())
&& Objects.equals(version, other.version());
}

/**
* Returns a builder for {@code Policy} objects.
*/
public static Builder builder() {
return new Builder();
}
}
Loading

0 comments on commit a493457

Please sign in to comment.