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 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