From c81ff3e057126b3efcdadaed7b9e3874c3d10071 Mon Sep 17 00:00:00 2001 From: Igor Bernstein Date: Tue, 4 Sep 2018 15:44:56 -0400 Subject: [PATCH] Bigtable: add resource level IAM (#3624) --- .../admin/v2/BigtableInstanceAdminClient.java | 286 ++++++++++++++++++ .../v2/BigtableInstanceAdminClientTest.java | 117 +++++++ 2 files changed, 403 insertions(+) diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java index f4f85639062b..24b8bfc5f8ef 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/main/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClient.java @@ -19,6 +19,7 @@ import com.google.api.core.ApiFunction; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutures; +import com.google.api.resourcenames.ResourceName; import com.google.bigtable.admin.v2.AppProfileName; import com.google.bigtable.admin.v2.ClusterName; import com.google.bigtable.admin.v2.DeleteAppProfileRequest; @@ -27,6 +28,8 @@ import com.google.bigtable.admin.v2.ListAppProfilesRequest; import com.google.bigtable.admin.v2.LocationName; import com.google.bigtable.admin.v2.ProjectName; +import com.google.cloud.Policy; +import com.google.cloud.Policy.DefaultMarshaller; import com.google.cloud.bigtable.admin.v2.BaseBigtableInstanceAdminClient.ListAppProfilesPage; import com.google.cloud.bigtable.admin.v2.BaseBigtableInstanceAdminClient.ListAppProfilesPagedResponse; import com.google.cloud.bigtable.admin.v2.models.AppProfile; @@ -46,8 +49,13 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.UncheckedExecutionException; +import com.google.iam.v1.GetIamPolicyRequest; +import com.google.iam.v1.SetIamPolicyRequest; +import com.google.iam.v1.TestIamPermissionsRequest; +import com.google.iam.v1.TestIamPermissionsResponse; import com.google.protobuf.Empty; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Objects; import javax.annotation.Nonnull; @@ -992,6 +1000,284 @@ public Void apply(Empty input) { ); } + /** + * Gets the IAM access control policy for the specified instance. + * + *

Sample code: + * + *

{@code
+   * Policy policy = client.getIamPolicy("my-instance");
+   * for(Map.Entry> entry : policy.getBindings().entrySet()) {
+   *   System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   * }
+   * }
+ * + * @see Instance-level + * IAM management + */ + @SuppressWarnings("WeakerAccess") + public Policy getIamPolicy(String instanceId) { + return awaitFuture(getIamPolicyAsync(instanceId)); + } + + /** + * Asynchronously gets the IAM access control policy for the specified instance. + * + *

Sample code: + * + *

{@code
+   * ApiFuture policyFuture = client.getIamPolicyAsync("my-instance");
+   *
+   * ApiFutures.addCallback(policyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture getIamPolicyAsync(String instanceId) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + + GetIamPolicyRequest request = GetIamPolicyRequest.newBuilder() + .setResource(name.toString()) + .build(); + + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); + + return ApiFutures.transform( + stub.getIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Replaces the IAM policy associated with the specified instance. + * + *

Sample code: + * + *

{@code
+   * Policy newPolicy = client.setIamPolicy("my-instance",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") + public Policy setIamPolicy(String instanceId, Policy policy) { + return awaitFuture(setIamPolicyAsync(instanceId, policy)); + } + + /** + * Asynchronously replaces the IAM policy associated with the specified instance. + * + *

Sample code: + * + *

{@code
+   * ApiFuture newPolicyFuture = client.setIamPolicyAsync("my-instance",
+   *   Policy.newBuilder()
+   *     .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com"))
+   *     .addIdentity(Role.of("bigtable.admin"), Identity.group("admins@example.com"))
+   *     .build());
+   *
+   * ApiFutures.addCallback(policyFuture,
+   *   new ApiFutureCallback() {
+   *     public void onSuccess(Policy policy) {
+   *       for (Entry> entry : policy.getBindings().entrySet()) {
+   *         System.out.printf("Role: %s Identities: %s\n", entry.getKey(), entry.getValue());
+   *       }
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Instance-level IAM management + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture setIamPolicyAsync(String instanceId, Policy policy) { + InstanceName name = InstanceName.of(projectName.getProject(), instanceId); + final IamPolicyMarshaller marshaller = new IamPolicyMarshaller(); + + SetIamPolicyRequest request = SetIamPolicyRequest.newBuilder() + .setResource(name.toString()) + .setPolicy(marshaller.toPb(policy)) + .build(); + + return ApiFutures.transform( + stub.setIamPolicyCallable().futureCall(request), + new ApiFunction() { + @Override + public Policy apply(com.google.iam.v1.Policy proto) { + return marshaller.fromPb(proto); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Tests whether the caller has the given permissions for the specified instance. + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * List grantedPermissions = client.testIamPermission("my-instance",
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   * }
+ * + * System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows")); + * System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows")); + * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public List testIamPermission(String instanceId, String... permissions) { + return testIamPermission(InstanceName.of(projectName.getProject(), instanceId), permissions); + } + + /** + * Asynchronously tests whether the caller has the given permissions for the specified instance. + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * ApiFuture> grantedPermissionsFuture = client.testIamPermission("my-instance",
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * ApiFutures.addCallback(grantedPermissionsFuture,
+   *   new ApiFutureCallback>() {
+   *     public void onSuccess(List grantedPermissions) {
+   *       System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   *       System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture> testIamPermissionAsync(String instanceId, String... permissions) { + return testIamPermissionAsync(InstanceName.of(projectName.getProject(), instanceId), permissions); + } + + /** + * Tests whether the caller has the given permissions for the specified absolute resource name + * (note that the current project of the client is ignored). + * + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * List grantedPermissions = client.testIamPermission(
+   *   TableName.of("my-project", "my-instance", "my-table"),
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   * System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public List testIamPermission(ResourceName resourceName, String... permissions) { + return awaitFuture(testIamPermissionAsync(resourceName, permissions)); + } + + + /** + * Asynchronously tests whether the caller has the given permissions for the the specified + * absolute resource name (note that the current project of the client is ignored). + * Returns a subset of the specified permissions that the caller has. + * + *

Sample code: + * + *

{@code
+   * ApiFuture> grantedPermissionsFuture = client.testIamPermission(
+   *   TableName.of("my-project", "my-instance", "my-table"),
+   *   "bigtable.tables.readRows", "bigtable.tables.mutateRows");
+   *
+   * ApiFutures.addCallback(grantedPermissionsFuture,
+   *   new ApiFutureCallback>() {
+   *     public void onSuccess(List grantedPermissions) {
+   *       System.out.println("Has read access: " + grantedPermissions.contains("bigtable.tables.readRows"));
+   *       System.out.println("Has write access: " + grantedPermissions.contains("bigtable.tables.mutateRows"));
+   *     }
+   *
+   *     public void onFailure(Throwable t) {
+   *       t.printStackTrace();
+   *     }
+   *   },
+   *   MoreExecutors.directExecutor());
+   * }
+ * + * @see Cloud Bigtable permissions + */ + @SuppressWarnings("WeakerAccess") + public ApiFuture> testIamPermissionAsync(ResourceName resourceName, String... permissions) { + TestIamPermissionsRequest request = TestIamPermissionsRequest.newBuilder() + .setResource(resourceName.toString()) + .addAllPermissions(Arrays.asList(permissions)) + .build(); + + return ApiFutures.transform( + stub.testIamPermissionsCallable().futureCall(request), + new ApiFunction>() { + @Override + public List apply(TestIamPermissionsResponse input) { + return input.getPermissionsList(); + } + }, + MoreExecutors.directExecutor() + ); + } + + /** + * Simple adapter to expose {@link DefaultMarshaller} to this class. It enables this client to + * convert to/from IAM wrappers and protobufs. + */ + private static class IamPolicyMarshaller extends DefaultMarshaller { + @Override + public Policy fromPb(com.google.iam.v1.Policy policyPb) { + return super.fromPb(policyPb); + } + + @Override + public com.google.iam.v1.Policy toPb(Policy policy) { + return super.toPb(policy); + } + } + /** * Awaits the result of a future, taking care to propagate errors while maintaining the call site * in a suppressed exception. This allows semantic errors to be caught across threads, while diff --git a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java index 487191f19768..afee494a1449 100644 --- a/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java +++ b/google-cloud-clients/google-cloud-bigtable-admin/src/test/java/com/google/cloud/bigtable/admin/v2/BigtableInstanceAdminClientTest.java @@ -36,6 +36,9 @@ import com.google.bigtable.admin.v2.StorageType; import com.google.bigtable.admin.v2.UpdateClusterMetadata; import com.google.bigtable.admin.v2.UpdateInstanceMetadata; +import com.google.cloud.Identity; +import com.google.cloud.Policy; +import com.google.cloud.Role; import com.google.cloud.bigtable.admin.v2.BaseBigtableInstanceAdminClient.ListAppProfilesPage; import com.google.cloud.bigtable.admin.v2.BaseBigtableInstanceAdminClient.ListAppProfilesPagedResponse; import com.google.cloud.bigtable.admin.v2.models.AppProfile; @@ -51,6 +54,8 @@ import com.google.cloud.bigtable.admin.v2.models.UpdateInstanceRequest; import com.google.cloud.bigtable.admin.v2.stub.BigtableInstanceAdminStub; import com.google.common.collect.Lists; +import com.google.common.io.BaseEncoding; +import com.google.protobuf.ByteString; import com.google.protobuf.Empty; import com.google.protobuf.FieldMask; import io.grpc.Status.Code; @@ -114,6 +119,12 @@ public class BigtableInstanceAdminClientTest { @Mock private UnaryCallable mockDeleteAppProfileCallable; + @Mock + private UnaryCallable mockGetIamPolicyCallable; + @Mock + private UnaryCallable mockSetIamPolicyCallable; + @Mock + private UnaryCallable mockTestIamPermissionsCallable; @Before public void setUp() { @@ -139,6 +150,10 @@ public void setUp() { Mockito.when(mockStub.updateAppProfileOperationCallable()) .thenReturn(mockUpdateAppProfileCallable); Mockito.when(mockStub.deleteAppProfileCallable()).thenReturn(mockDeleteAppProfileCallable); + + Mockito.when(mockStub.getIamPolicyCallable()).thenReturn(mockGetIamPolicyCallable); + Mockito.when(mockStub.setIamPolicyCallable()).thenReturn(mockSetIamPolicyCallable); + Mockito.when(mockStub.testIamPermissionsCallable()).thenReturn(mockTestIamPermissionsCallable); } @Test @@ -726,4 +741,106 @@ private void mockOperationResult( .immediateOperationFuture(operationSnapshot); Mockito.when(callable.futureCall(request)).thenReturn(operationFuture); } + + @Test + public void testGetIamPolicy() { + // Setup + com.google.iam.v1.GetIamPolicyRequest expectedRequest = + com.google.iam.v1.GetIamPolicyRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockGetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = adminClient.getIamPolicy(INSTANCE_NAME.getInstance()); + + // Verify + assertThat(actualResult).isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build() + ); + } + + @Test + public void testSetIamPolicy() { + // Setup + com.google.iam.v1.SetIamPolicyRequest expectedRequest = + com.google.iam.v1.SetIamPolicyRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .setPolicy( + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + ) + .build(); + + com.google.iam.v1.Policy expectedResponse = + com.google.iam.v1.Policy.newBuilder() + .addBindings( + com.google.iam.v1.Binding.newBuilder() + .setRole("roles/bigtable.user") + .addMembers("user:someone@example.com") + ) + .setEtag(ByteString.copyFromUtf8("my-etag")) + .build(); + + Mockito.when(mockSetIamPolicyCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + Policy actualResult = adminClient.setIamPolicy(INSTANCE_NAME.getInstance(), + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .build()); + + // Verify + assertThat(actualResult).isEqualTo( + Policy.newBuilder() + .addIdentity(Role.of("bigtable.user"), Identity.user("someone@example.com")) + .setEtag(BaseEncoding.base64().encode("my-etag".getBytes())) + .build() + ); + } + + @Test + public void testTestIamPermissions() { + // Setup + com.google.iam.v1.TestIamPermissionsRequest expectedRequest = + com.google.iam.v1.TestIamPermissionsRequest.newBuilder() + .setResource(INSTANCE_NAME.toString()) + .addPermissions("bigtable.tables.readRows") + .build(); + + com.google.iam.v1.TestIamPermissionsResponse expectedResponse = + com.google.iam.v1.TestIamPermissionsResponse.newBuilder() + .addPermissions("bigtable.tables.readRows") + .build(); + + Mockito.when(mockTestIamPermissionsCallable.futureCall(expectedRequest)) + .thenReturn(ApiFutures.immediateFuture(expectedResponse)); + + // Execute + List actualResult = adminClient.testIamPermission(INSTANCE_NAME.getInstance(), + "bigtable.tables.readRows"); + + // Verify + assertThat(actualResult).containsExactly("bigtable.tables.readRows"); + } }