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

feat: add Dataset ACL support #1763

Merged
merged 12 commits into from Feb 2, 2022
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 Dataset(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor constructor surprise for me, until I realized we're dealing with an ACL.Dataset and not a Dataset. I suspect this is just local ambiguity; any other places where we need to consider the naming?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah I agree it's a little confusing so I renamed it to be more explicit: 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,64 @@ static Entity fromPb(Access access) {
}
}

/**
* Class for a BigQuery Dataset entity. Objects of this class represent a dataset from a different
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe clarify this is a Dataset ACL entity, not a Dataset entity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. Thanks!

* dataset to grant access to. Only views are supported for now. The role field is not required
* when this field is set. If that dataset is deleted and re-created, its access needs to be
* granted again via an update operation.
*/
public static final class Dataset extends Entity {

private static final long serialVersionUID = -8392885851733136526L;

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

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

/** Returns dataset'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;
}
Dataset dataset = (Dataset) obj;
return Objects.equals(getType(), dataset.getType()) && Objects.equals(id, dataset.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 @@ -516,6 +582,11 @@ public static Acl of(Entity entity, Role role) {
return new Acl(entity, role);
}

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

/** Returns an Acl object for a view entity. */
public static Acl of(View view) {
return new Acl(view, null);
Expand Down
Expand Up @@ -27,10 +27,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");
Acl.Dataset entity = new Acl.Dataset(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 @@ -38,8 +38,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 +48,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 Acl.Dataset(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 +231,7 @@ protected Serializable[] serializableObjects() {
USER_ACCESS,
VIEW_ACCESS,
ROUTINE_ACCESS,
DATASET_ACCESS,
DATASET_ID,
DATASET_INFO,
TABLE_ID,
Expand Down
Expand Up @@ -1897,6 +1897,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
Acl.Dataset datasetEntity = new Acl.Dataset(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