Skip to content

Commit ca49f40

Browse files
feat(bigquery): Add samples for control access 2/3 (#4024)
* feat(bigquery): Add samples for control access 2/3 * chore(bigquery): testing, stylistic update * fix(bigquery): update principalId content * chore(bigquery): simplify logging logic in revokeTableOrViewAccess --------- Co-authored-by: Eric Schmidt <[email protected]>
1 parent 12dd1f6 commit ca49f40

File tree

4 files changed

+362
-0
lines changed

4 files changed

+362
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
async function main(datasetId, entityId) {
18+
// [START bigquery_revoke_dataset_access]
19+
20+
/**
21+
* TODO(developer): Update and un-comment below lines
22+
*/
23+
24+
// const datasetId = "my_project_id.my_dataset"
25+
26+
// ID of the user or group from whom you are revoking access.
27+
// const entityId = "[email protected]"
28+
29+
const {BigQuery} = require('@google-cloud/bigquery');
30+
31+
// Instantiate a client.
32+
const bigquery = new BigQuery();
33+
34+
async function revokeDatasetAccess() {
35+
const [dataset] = await bigquery.dataset(datasetId).get();
36+
37+
// To revoke access to a dataset, remove elements from the access list.
38+
//
39+
// See the BigQuery client library documentation for more details on access entries:
40+
// https://cloud.google.com/nodejs/docs/reference/bigquery/latest
41+
42+
// Filter access entries to exclude entries matching the specified entity_id
43+
// and assign a new list back to the access list.
44+
dataset.metadata.access = dataset.metadata.access.filter(entry => {
45+
return !(
46+
entry.entity_id === entityId ||
47+
entry.userByEmail === entityId ||
48+
entry.groupByEmail === entityId
49+
);
50+
});
51+
52+
// Update will only succeed if the dataset
53+
// has not been modified externally since retrieval.
54+
//
55+
// See the BigQuery client library documentation for more details on metadata updates:
56+
// https://cloud.google.com/bigquery/docs/updating-datasets
57+
58+
// Update just the 'access entries' property of the dataset.
59+
await dataset.setMetadata(dataset.metadata);
60+
61+
console.log(`Revoked access to '${entityId}' from '${datasetId}'.`);
62+
}
63+
// [END bigquery_revoke_dataset_access]
64+
await revokeDatasetAccess();
65+
}
66+
67+
exports.revokeDatasetAccess = main;
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
async function main(
18+
projectId,
19+
datasetId,
20+
tableId,
21+
roleToRemove = null,
22+
principalToRemove = null
23+
) {
24+
// [START bigquery_revoke_access_to_table_or_view]
25+
26+
/**
27+
* TODO(developer): Update and un-comment below lines
28+
*/
29+
// const projectId = "YOUR_PROJECT_ID"
30+
// const datasetId = "YOUR_DATASET_ID"
31+
// const tableId = "YOUR_TABLE_ID"
32+
// const roleToRemove = "YOUR_ROLE"
33+
// const principalToRemove = "YOUR_PRINCIPAL_ID"
34+
35+
const {BigQuery} = require('@google-cloud/bigquery');
36+
37+
// Instantiate a client.
38+
const client = new BigQuery();
39+
40+
async function revokeAccessToTableOrView() {
41+
const dataset = client.dataset(datasetId);
42+
const table = dataset.table(tableId);
43+
44+
// Get the IAM access policy for the table or view.
45+
const [policy] = await table.getIamPolicy();
46+
47+
// Initialize bindings array.
48+
if (!policy.bindings) {
49+
policy.bindings = [];
50+
}
51+
52+
// To revoke access to a table or view,
53+
// remove bindings from the Table or View policy.
54+
//
55+
// Find more details about Policy objects here:
56+
// https://cloud.google.com/security-command-center/docs/reference/rest/Shared.Types/Policy
57+
58+
if (principalToRemove) {
59+
// Create a copy of bindings for modifications.
60+
const bindings = [...policy.bindings];
61+
62+
// Filter out the principal from each binding.
63+
for (const binding of bindings) {
64+
if (binding.members) {
65+
binding.members = binding.members.filter(
66+
m => m !== principalToRemove
67+
);
68+
}
69+
}
70+
71+
// Filter out bindings with empty members.
72+
policy.bindings = bindings.filter(
73+
binding => binding.members && binding.members.length > 0
74+
);
75+
}
76+
77+
if (roleToRemove) {
78+
// Filter out all bindings with the roleToRemove
79+
// and assign a new list back to the policy bindings.
80+
policy.bindings = policy.bindings.filter(b => b.role !== roleToRemove);
81+
}
82+
83+
// Set the IAM access policy with updated bindings.
84+
await table.setIamPolicy(policy);
85+
86+
// Both role and principal are removed
87+
if (roleToRemove !== null && principalToRemove !== null) {
88+
console.log(
89+
`Role '${roleToRemove}' revoked for principal '${principalToRemove}' on resource '${datasetId}.${tableId}'.`
90+
);
91+
}
92+
93+
// Only role is removed
94+
if (roleToRemove !== null && principalToRemove === null) {
95+
console.log(
96+
`Role '${roleToRemove}' revoked for all principals on resource '${datasetId}.${tableId}'.`
97+
);
98+
}
99+
100+
// Only principal is removed
101+
if (roleToRemove === null && principalToRemove !== null) {
102+
console.log(
103+
`Access revoked for principal '${principalToRemove}' on resource '${datasetId}.${tableId}'.`
104+
);
105+
}
106+
107+
// No changes were made
108+
if (roleToRemove === null && principalToRemove === null) {
109+
console.log(
110+
`No changes made to access policy for '${datasetId}.${tableId}'.`
111+
);
112+
}
113+
}
114+
// [END bigquery_revoke_access_to_table_or_view]
115+
await revokeAccessToTableOrView();
116+
}
117+
118+
exports.revokeAccessToTableOrView = main;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const {beforeEach, afterEach, it, describe} = require('mocha');
18+
const assert = require('assert');
19+
const sinon = require('sinon');
20+
21+
const {setupBeforeAll, cleanupResources} = require('./config');
22+
const {grantAccessToDataset} = require('../grantAccessToDataset');
23+
const {revokeDatasetAccess} = require('../revokeDatasetAccess');
24+
25+
describe('revokeDatasetAccess', () => {
26+
let datasetId = null;
27+
let entityId = null;
28+
const role = 'READER';
29+
30+
beforeEach(async () => {
31+
const response = await setupBeforeAll();
32+
datasetId = response.datasetId;
33+
entityId = response.entityId;
34+
35+
sinon.stub(console, 'log');
36+
sinon.stub(console, 'error');
37+
});
38+
39+
// Clean up after all tests.
40+
afterEach(async () => {
41+
await cleanupResources(datasetId);
42+
console.log.restore();
43+
console.error.restore();
44+
});
45+
46+
it('should revoke access to a dataset', async () => {
47+
// Grant access to the dataset.
48+
await grantAccessToDataset(datasetId, entityId, role);
49+
50+
// Reset console.log stub to clear the history of calls
51+
console.log.resetHistory();
52+
53+
// Now revoke access.
54+
await revokeDatasetAccess(datasetId, entityId);
55+
56+
// Check if the right message was logged.
57+
assert.strictEqual(
58+
console.log.calledWith(
59+
`Revoked access to '${entityId}' from '${datasetId}'.`
60+
),
61+
true
62+
);
63+
});
64+
});
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
'use strict';
16+
17+
const {describe, it, beforeEach, afterEach} = require('mocha');
18+
const assert = require('assert');
19+
const sinon = require('sinon');
20+
21+
const {revokeAccessToTableOrView} = require('../revokeTableOrViewAccess');
22+
const {grantAccessToTableOrView} = require('../grantAccessToTableOrView');
23+
const {setupBeforeAll, cleanupResources} = require('./config');
24+
25+
describe('revokeTableOrViewAccess', () => {
26+
let datasetId = null;
27+
let tableId = null;
28+
let entityId = null;
29+
const projectId = process.env.GCLOUD_PROJECT;
30+
const roleId = 'roles/bigquery.dataViewer';
31+
32+
beforeEach(async () => {
33+
const response = await setupBeforeAll();
34+
datasetId = response.datasetId;
35+
tableId = response.tableId;
36+
entityId = response.entityId;
37+
38+
sinon.stub(console, 'log');
39+
sinon.stub(console, 'error');
40+
});
41+
42+
afterEach(async () => {
43+
await cleanupResources(datasetId);
44+
console.log.restore();
45+
console.error.restore();
46+
});
47+
48+
it('should revoke access to a table for a specific role', async () => {
49+
const principalId = `group:${entityId}`;
50+
51+
// Grant access first.
52+
await grantAccessToTableOrView(
53+
projectId,
54+
datasetId,
55+
tableId,
56+
principalId,
57+
roleId
58+
);
59+
60+
// Reset console log history.
61+
console.log.resetHistory();
62+
63+
// Revoke access for the role.
64+
await revokeAccessToTableOrView(
65+
projectId,
66+
datasetId,
67+
tableId,
68+
roleId,
69+
null
70+
);
71+
72+
// Check that the right message was logged.
73+
assert.strictEqual(
74+
console.log.calledWith(
75+
`Role '${roleId}' revoked for all principals on resource '${datasetId}.${tableId}'.`
76+
),
77+
true
78+
);
79+
});
80+
81+
it('should revoke access to a table for a specific principal', async () => {
82+
const principalId = `group:${entityId}`;
83+
84+
// Grant access first.
85+
await grantAccessToTableOrView(
86+
projectId,
87+
datasetId,
88+
tableId,
89+
principalId,
90+
roleId
91+
);
92+
93+
// Reset console log history.
94+
console.log.resetHistory();
95+
96+
// Revoke access for the principal.
97+
await revokeAccessToTableOrView(
98+
projectId,
99+
datasetId,
100+
tableId,
101+
null,
102+
principalId
103+
);
104+
105+
// Check that the right message was logged.
106+
assert.strictEqual(
107+
console.log.calledWith(
108+
`Access revoked for principal '${principalId}' on resource '${datasetId}.${tableId}'.`
109+
),
110+
true
111+
);
112+
});
113+
});

0 commit comments

Comments
 (0)