Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions bigquery/cloud-client/revokeDatasetAccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

/**
* Revokes access to a dataset for a specified entity.
*
* @param {string} datasetId ID of the dataset to revoke access to.
* @param {string} entityId ID of the user or group from whom you are revoking access.
* Alternatively, the JSON REST API representation of the entity,
* such as a view's table reference.
* @returns {Promise<Array>} A promise that resolves to the updated access entries.
*/
async function revokeDatasetAccess(datasetId, entityId) {
// [START bigquery_revoke_dataset_access]
const {BigQuery} = require('@google-cloud/bigquery');

// Define enum for HTTP codes.
const HTTP_STATUS = {
PRECONDITION_FAILED: 412,
};

// TODO (developer): Update and un-comment below lines.

// ID of the dataset to revoke access to.
// datasetId = "my_project.my_dataset"

// ID of the user or group from whom you are revoking access.
// Alternatively, the JSON REST API representation of the entity,
// such as a view's table reference.
// entityId = "[email protected]"

// Instantiate a client.
const bigquery = new BigQuery();

// Get a reference to the dataset.
const [dataset] = await bigquery.dataset(datasetId).get();

// To revoke access to a dataset, remove elements from the access array.
//
// See the BigQuery client library documentation for more details on access entries:
// https://cloud.google.com/nodejs/docs/reference/secret-manager/4.1.4

// Filter access entries to exclude entries matching the specified entity_id
// and assign a new array back to the access array.
dataset.metadata.access = dataset.metadata.access.filter(entry => {
// Return false (remove entry) if any of these fields match entityId.
return !(
entry.entity_id === entityId ||
entry.userByEmail === entityId ||
entry.groupByEmail === entityId
);
});

// Update will only succeed if the dataset
// has not been modified externally since retrieval.

try {
// Update just the access entries property of the dataset.
const [updatedDataset] = await dataset.setMetadata(dataset.metadata);

return updatedDataset.access;
} catch (error) {
// Check if it's a precondition failed error (a read-modify-write error).
if (error.code === HTTP_STATUS.PRECONDITION_FAILED) {
console.log(
`Dataset '${dataset.id}' was modified remotely before this update. ` +
'Fetch the latest version and retry.'
);
} else {
throw error;
}
}
// [END bigquery_revoke_dataset_access]
}

module.exports = {
revokeDatasetAccess,
};
119 changes: 119 additions & 0 deletions bigquery/cloud-client/revokeTableOrViewAccess.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

/**
* Revokes access to a BigQuery table or view.
* @param {string} projectId The ID of the Google Cloud project.
* @param {string} datasetId The ID of the dataset containing the table/view.
* @param {string} resourceName The ID of the table or view.
* @param {string} [roleToRemove=null] Optional. Specific role to revoke.
* @param {string} [principalToRemove=null] Optional. Specific principal to revoke access from.
* @returns {Promise<Array>} The updated IAM policy.
*/
async function revokeAccessToTableOrView(
projectId,
datasetId,
resourceName,
roleToRemove = null,
principalToRemove = null
) {
// [START bigquery_revoke_access_to_table_or_view]
const {BigQuery} = require('@google-cloud/bigquery');

// TODO (developer): Update and un-comment below lines.

// Google Cloud Platform project.
// projectId = "my_project_id"

// Dataset where the table or view is.
// datasetId = "my_dataset_id"

// Table or view name to get the access policy.
// resourceName = "my_table_id"

// (Optional) Role to remove from the table or view.
// roleToRemove = "roles/bigquery.dataViewer"

// (Optional) Principal to remove from the table or view.
// principalToRemove = "user:[email protected]"

// Find more information about roles and principals (refered as members) here:
// https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Binding

// Instantiate a client.
const client = new BigQuery();

// Get a reference to the dataset by datasetId.
const dataset = client.dataset(datasetId);
// Get a reference to the table by tableName.
const table = dataset.table(resourceName);

// Get the IAM access policy for the table or view.
const [policy] = await table.getIamPolicy();

// Initialize bindings array.
if (!policy.bindings) {
policy.bindings = [];
}

// To revoke access to a table or view,
// remove bindings from the Table or View policy.
//
// Find more details about Policy objects here:
// https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Policy

if (roleToRemove) {
// Filter out all bindings with the `roleToRemove`
// and assign a new array back to the policy bindings.
policy.bindings = policy.bindings.filter(b => b.role !== roleToRemove);
}

if (principalToRemove) {
// The `bindings` array is immutable. Create a copy for modifications.
const bindings = [...policy.bindings];

// Filter out the principal from each binding.
for (const binding of bindings) {
if (binding.members) {
binding.members = binding.members.filter(m => m !== principalToRemove);
}
}

// Filter out bindings with empty members.
policy.bindings = bindings.filter(
binding => binding.members && binding.members.length > 0
);
}

try {
// Set the IAM access policy with updated bindings.
await table.setIamPolicy(policy);

// Get the policy again to confirm it's set correctly.
const [verifiedPolicy] = await table.getIamPolicy();

// Return the updated policy bindings.
return verifiedPolicy && verifiedPolicy.bindings
? verifiedPolicy.bindings
: [];
} catch (error) {
console.error('Error settings IAM policy:', error);
throw error;
}
// [END bigquery_revoke_access_to_table_or_view]
}

module.exports = {revokeAccessToTableOrView};
103 changes: 103 additions & 0 deletions bigquery/cloud-client/test/revokeDatasetAccess.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Copyright 2025 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

const {describe, it, before, after} = require('mocha');
const assert = require('assert');
const {grantAccessToDataset} = require('../grantAccessToDataset');
const {revokeDatasetAccess} = require('../revokeDatasetAccess');
const {
getDataset,
getEntityId,
setupBeforeAll,
teardownAfterAll,
} = require('./config');

describe('revokeDatasetAccess', () => {
// Setup resources before all tests.
before(async () => {
await setupBeforeAll();
});

// Clean up resources after all tests.
after(async () => {
await teardownAfterAll();
});

it('should revoke access to a dataset', async () => {
// Get test resources.
const dataset = await getDataset();
const entityId = getEntityId();

// Directly use the dataset ID.
const datasetId = dataset.id;
console.log(`Testing with dataset: ${datasetId} and entity: ${entityId}`);

// First grant access to the dataset.
const datasetAccessEntries = await grantAccessToDataset(
datasetId,
entityId,
'READER'
);

// Create a set of all entity IDs and email addresses to check.
const datasetEntityIds = new Set();
datasetAccessEntries.forEach(entry => {
if (entry.entity_id) {
datasetEntityIds.add(entry.entity_id);
}
if (entry.userByEmail) {
datasetEntityIds.add(entry.userByEmail);
}
if (entry.groupByEmail) {
datasetEntityIds.add(entry.groupByEmail);
}
});

// Check if our entity ID is in the set.
const hasAccess = datasetEntityIds.has(entityId);
console.log(`Entity ${entityId} has access after granting: ${hasAccess}`);
assert.strictEqual(
hasAccess,
true,
'Entity should have access after granting'
);

// Now revoke access.
const newAccessEntries = await revokeDatasetAccess(datasetId, entityId);

// Check that the entity no longer has access.
const updatedEntityIds = new Set();
newAccessEntries.forEach(entry => {
if (entry.entity_id) {
updatedEntityIds.add(entry.entity_id);
}
if (entry.userByEmail) {
updatedEntityIds.add(entry.userByEmail);
}
if (entry.groupByEmail) {
updatedEntityIds.add(entry.groupByEmail);
}
});

const stillHasAccess = updatedEntityIds.has(entityId);
console.log(
`Entity ${entityId} has access after revoking: ${stillHasAccess}`
);
assert.strictEqual(
stillHasAccess,
false,
'Entity should not have access after revoking'
);
});
});
Loading
Loading