Skip to content

Commit

Permalink
feat: add Dataset ACL support (#1763)
Browse files Browse the repository at this point in the history
* feat: add Dataset ACL support

* some lint updates

* add back interface that was causing IDE issues

* fix recursive import issue

* debug

* update code based on pending discovery doc changes (cl/421673680)

* update code to reflect more changes (target_types should be List<String>) based on pending discovery doc changes (cl/421673680)

* 🦉 Updates from OwlBot

See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md

* rename to address feedback

Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
  • Loading branch information
stephaniewang526 and gcf-owl-bot[bot] committed Feb 2, 2022
1 parent 0fe0a78 commit 18a11e8
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 10 deletions.
Expand Up @@ -20,9 +20,11 @@

import com.google.api.core.ApiFunction;
import com.google.api.services.bigquery.model.Dataset.Access;
import com.google.api.services.bigquery.model.DatasetAccessEntry;
import com.google.cloud.StringEnumType;
import com.google.cloud.StringEnumValue;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;

/**
Expand Down Expand Up @@ -105,7 +107,8 @@ public enum Type {
USER,
VIEW,
IAM_MEMBER,
ROUTINE
ROUTINE,
DATASET
}

Entity(Type type) {
Expand All @@ -119,6 +122,11 @@ public Type getType() {
abstract Access toPb();

static Entity fromPb(Access access) {
if (access.getDataset() != null) {
return new DatasetAclEntity(
DatasetId.fromPb(access.getDataset().getDataset()),
access.getDataset().getTargetTypes());
}
if (access.getDomain() != null) {
return new Domain(access.getDomain());
}
Expand Down Expand Up @@ -146,6 +154,65 @@ static Entity fromPb(Access access) {
}
}

/**
* Class for a BigQuery DatasetAclEntity ACL entity. Objects of this class represent a
* DatasetAclEntity from a different DatasetAclEntity to grant access to. Only views are supported
* for now. The role field is not required when this field is set. If that DatasetAclEntity is
* deleted and re-created, its access needs to be granted again via an update operation.
*/
public static final class DatasetAclEntity extends Entity {

private static final long serialVersionUID = -8392885851733136526L;

private final DatasetId id;
private final List<String> targetTypes;

/** Creates a DatasetAclEntity given the DatasetAclEntity's id. */
public DatasetAclEntity(DatasetId id, List<String> targetTypes) {
super(Type.DATASET);
this.id = id;
this.targetTypes = targetTypes;
}

/** Returns DatasetAclEntity's identity. */
public DatasetId getId() {
return id;
}

public List<String> getTargetTypes() {
return targetTypes;
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
DatasetAclEntity datasetAclEntity = (DatasetAclEntity) obj;
return Objects.equals(getType(), datasetAclEntity.getType())
&& Objects.equals(id, datasetAclEntity.id);
}

@Override
public int hashCode() {
return Objects.hash(getType(), id);
}

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

@Override
Access toPb() {
return new Access()
.setDataset(new DatasetAccessEntry().setDataset(id.toPb()).setTargetTypes(targetTypes));
}
}

/**
* Class for a BigQuery Domain entity. Objects of this class represent a domain to grant access
* to. Any users signed in with the domain specified will be granted the specified access.
Expand Down Expand Up @@ -342,9 +409,10 @@ Access toPb() {

/**
* Class for a BigQuery View entity. Objects of this class represent a view from a different
* dataset to grant access to. Queries executed against that view will have read access to tables
* in this dataset. The role field is not required when this field is set. If that view is updated
* by any user, access to the view needs to be granted again via an update operation.
* datasetAclEntity to grant access to. Queries executed against that view will have read access
* to tables in this datasetAclEntity. The role field is not required when this field is set. If
* that view is updated by any user, access to the view needs to be granted again via an update
* operation.
*/
public static final class View extends Entity {

Expand Down Expand Up @@ -393,10 +461,10 @@ Access toPb() {

/**
* Class for a BigQuery Routine entity. Objects of this class represent a routine from a different
* dataset to grant access to. Queries executed against that routine will have read access to
* views/tables/routines in this dataset. Only UDF is supported for now. The role field is not
* required when this field is set. If that routine is updated by any user, access to the routine
* needs to be granted again via an update operation.
* datasetAclEntity to grant access to. Queries executed against that routine will have read
* access to views/tables/routines in this datasetAclEntity. Only UDF is supported for now. The
* role field is not required when this field is set. If that routine is updated by any user,
* access to the routine needs to be granted again via an update operation.
*/
public static final class Routine extends Entity {

Expand Down Expand Up @@ -516,6 +584,11 @@ public static Acl of(Entity entity, Role role) {
return new Acl(entity, role);
}

/** Returns an Acl object for a datasetAclEntity. */
public static Acl of(DatasetAclEntity datasetAclEntity) {
return new Acl(datasetAclEntity, null);
}

/** Returns an Acl object for a view entity. */
public static Acl of(View view) {
return new Acl(view, null);
Expand Down
Expand Up @@ -19,6 +19,7 @@
import static org.junit.Assert.assertEquals;

import com.google.api.services.bigquery.model.Dataset;
import com.google.cloud.bigquery.Acl.DatasetAclEntity;
import com.google.cloud.bigquery.Acl.Domain;
import com.google.cloud.bigquery.Acl.Entity;
import com.google.cloud.bigquery.Acl.Entity.Type;
Expand All @@ -27,10 +28,23 @@
import com.google.cloud.bigquery.Acl.Role;
import com.google.cloud.bigquery.Acl.User;
import com.google.cloud.bigquery.Acl.View;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Test;

public class AclTest {

@Test
public void testDatasetEntity() {
DatasetId datasetId = DatasetId.of("dataset");
List<String> targetTypes = ImmutableList.of("VIEWS");
DatasetAclEntity entity = new DatasetAclEntity(datasetId, targetTypes);
assertEquals(datasetId, entity.getId());
assertEquals(targetTypes, entity.getTargetTypes());
Dataset.Access pb = entity.toPb();
assertEquals(entity, Entity.fromPb(pb));
}

@Test
public void testDomainEntity() {
Domain entity = new Domain("d1");
Expand Down
Expand Up @@ -20,6 +20,7 @@
import com.google.cloud.NoCredentials;
import com.google.cloud.PageImpl;
import com.google.cloud.Restorable;
import com.google.cloud.bigquery.Acl.DatasetAclEntity;
import com.google.cloud.bigquery.StandardTableDefinition.StreamingBuffer;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
Expand All @@ -38,8 +39,6 @@ public class SerializationTest extends BaseSerializationTest {
Acl.of(new Acl.View(TableId.of("project", "dataset", "table")), Acl.Role.WRITER);
private static final Acl ROUTINE_ACCESS =
Acl.of(new Acl.Routine(RoutineId.of("project", "dataset", "routine")), Acl.Role.WRITER);
private static final List<Acl> ACCESS_RULES =
ImmutableList.of(DOMAIN_ACCESS, GROUP_ACCESS, VIEW_ACCESS, ROUTINE_ACCESS, USER_ACCESS);
private static final Long CREATION_TIME = System.currentTimeMillis() - 10;
private static final Long DEFAULT_TABLE_EXPIRATION = 100L;
private static final String DESCRIPTION = "Description";
Expand All @@ -50,6 +49,11 @@ public class SerializationTest extends BaseSerializationTest {
private static final String LOCATION = "";
private static final String SELF_LINK = "http://bigquery/p/d";
private static final DatasetId DATASET_ID = DatasetId.of("project", "dataset");
private static final List<String> TARGET_TYPES = ImmutableList.of("VIEWS");
private static final Acl DATASET_ACCESS = Acl.of(new DatasetAclEntity(DATASET_ID, TARGET_TYPES));
private static final List<Acl> ACCESS_RULES =
ImmutableList.of(
DOMAIN_ACCESS, GROUP_ACCESS, VIEW_ACCESS, ROUTINE_ACCESS, USER_ACCESS, DATASET_ACCESS);
private static final DatasetInfo DATASET_INFO =
DatasetInfo.newBuilder(DATASET_ID)
.setAcl(ACCESS_RULES)
Expand Down Expand Up @@ -228,6 +232,7 @@ protected Serializable[] serializableObjects() {
USER_ACCESS,
VIEW_ACCESS,
ROUTINE_ACCESS,
DATASET_ACCESS,
DATASET_ID,
DATASET_INFO,
TABLE_ID,
Expand Down
Expand Up @@ -38,6 +38,7 @@
import com.google.cloud.Role;
import com.google.cloud.ServiceOptions;
import com.google.cloud.bigquery.Acl;
import com.google.cloud.bigquery.Acl.DatasetAclEntity;
import com.google.cloud.bigquery.BigQuery;
import com.google.cloud.bigquery.BigQuery.DatasetDeleteOption;
import com.google.cloud.bigquery.BigQuery.DatasetField;
Expand Down Expand Up @@ -1897,6 +1898,47 @@ public void testAuthorizeRoutine() {
assertEquals(routineAcl, routineDataset.getAcl());
}

@Test
public void testAuthorizeDataset() {
String datasetName = RemoteBigQueryHelper.generateDatasetName();
DatasetId datasetId = DatasetId.of(PROJECT_ID, datasetName);
List<String> targetTypes = ImmutableList.of("VIEWS");
// Specify the acl which will be shared to the authorized dataset
List<Acl> acl =
ImmutableList.of(
Acl.of(new Acl.Group("projectOwners"), Acl.Role.OWNER),
Acl.of(new Acl.IamMember("allUsers"), Acl.Role.READER));
DatasetInfo datasetInfo =
DatasetInfo.newBuilder(datasetId).setAcl(acl).setDescription("shared Dataset").build();
Dataset sharedDataset = bigquery.create(datasetInfo);
assertNotNull(sharedDataset);
assertEquals(sharedDataset.getDescription(), "shared Dataset");
// Get the current metadata for the dataset you want to share by calling the datasets.get method
List<Acl> sharedDatasetAcl = new ArrayList<>(sharedDataset.getAcl());

// Create a new dataset to be authorized
String authorizedDatasetName = RemoteBigQueryHelper.generateDatasetName();
DatasetId authorizedDatasetId = DatasetId.of(PROJECT_ID, authorizedDatasetName);
DatasetInfo authorizedDatasetInfo =
DatasetInfo.newBuilder(authorizedDatasetId)
.setDescription("new Dataset to be authorized by the sharedDataset")
.build();
Dataset authorizedDataset = bigquery.create(authorizedDatasetInfo);
assertNotNull(authorizedDataset);
assertEquals(
authorizedDataset.getDescription(), "new Dataset to be authorized by the sharedDataset");

// Add the new DatasetAccessEntry object to the existing sharedDatasetAcl list
DatasetAclEntity datasetEntity = new DatasetAclEntity(authorizedDatasetId, targetTypes);
sharedDatasetAcl.add(Acl.of(datasetEntity));

// Update the dataset with the added authorization
Dataset updatedDataset = sharedDataset.toBuilder().setAcl(sharedDatasetAcl).build().update();

// Verify that the authorized dataset has been added
assertEquals(sharedDatasetAcl, updatedDataset.getAcl());
}

@Test
public void testSingleStatementsQueryException() throws InterruptedException {
String invalidQuery =
Expand Down

0 comments on commit 18a11e8

Please sign in to comment.